]> source.dussan.org Git - poi.git/commitdiff
Added implementation of Digital Signature support using code initially developed...
authorUgo Cei <ugo@apache.org>
Tue, 13 Oct 2009 16:31:28 +0000 (16:31 +0000)
committerUgo Cei <ugo@apache.org>
Tue, 13 Oct 2009 16:31:28 +0000 (16:31 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@824836 13f79535-47bb-0310-9956-ffa450edef68

39 files changed:
build.xml
legal/NOTICE
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/AbstractXmlSignatureService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/KeyInfoKeySelector.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseInputStream.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseOutputStream.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/SignatureAspect.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/TemporaryDataStorage.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/AbstractOOXMLSignatureService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLProvider.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureAspect.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureVerifier.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLURIDereferencer.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipComparator.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformParameterSpec.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java [new file with mode: 0644]
src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx [new file with mode: 0644]
src/ooxml/testcases/hello-world-office-2010-technical-preview.docx [new file with mode: 0644]
src/ooxml/testcases/hello-world-signed-twice.docx [new file with mode: 0644]
src/ooxml/testcases/hello-world-signed.docx [new file with mode: 0644]
src/ooxml/testcases/hello-world-signed.pptx [new file with mode: 0644]
src/ooxml/testcases/hello-world-signed.xlsx [new file with mode: 0644]
src/ooxml/testcases/hello-world-unsigned.docx [new file with mode: 0644]
src/ooxml/testcases/hello-world-unsigned.pptx [new file with mode: 0644]
src/ooxml/testcases/hello-world-unsigned.xlsx [new file with mode: 0644]
src/ooxml/testcases/invalidsig.docx [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java [new file with mode: 0644]

index 6feaf34826538a3033476fb4d19aa53b7df25ffc..8b9b3550d10d6bce61b2fb4e4e1e41e227440101 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -132,6 +132,21 @@ 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.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.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"/>
+  <property name="ooxml.joda-time.jar" location="${ooxml.lib}/joda-time-1.6.jar"/>
+  <property name="ooxml.joda-time.url" value="${repository.m2}/maven2/joda-time/joda-time/1.6/joda-time-1.6.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 -->
   <!-- "Copy these file(s), free of charge" -->
@@ -183,6 +198,9 @@ under the License.
         <pathelement location="${scratchpad.output.test.dir}"/>
         <pathelement location="${contrib.output.dir}"/>
         <pathelement location="${contrib.output.test.dir}"/>
+        <fileset dir="${ooxml.lib}">
+                       <include name="*.jar" />
+               </fileset>
     </path>
 
     <path id="ooxml.classpath">
@@ -208,6 +226,7 @@ under the License.
         <pathelement location="${main.output.test.dir}"/> <!-- ooxml tests use some utilities from main tests -->
         <pathelement location="${scratchpad.output.test.dir}"/> 
         <pathelement location="${junit.jar1.dir}"/>
+        <pathelement location="${ooxml.src.test}"/>
     </path>
 
 
@@ -351,6 +370,13 @@ under the License.
                     <available file="${ooxml.xmlbeans.jar}"/>
                     <available file="${ooxml.jsr173.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.joda-time.jar}"/>
+                    <available file="${ooxml.bcprov.jar}"/>
                 </and>
                 <isset property="disconnected"/>
             </or>
@@ -373,6 +399,34 @@ under the License.
         <param name="sourcefile" value="${ooxml.schemas.url}"/>
         <param name="destfile" value="${ooxml.schemas.jar}"/>
       </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.joda-time.url}"/>
+        <param name="destfile" value="${ooxml.joda-time.jar}"/>
+      </antcall>
+      <antcall target="downloadfile">
+        <param name="sourcefile" value="${ooxml.bcprov.url}"/>
+        <param name="destfile" value="${ooxml.bcprov.jar}"/>
+      </antcall>
     </target>
 
          <target name="check-ooxml-xsds">
index 6d9855fb87ddfab808a579ff862c8dd81cefbada..dc948c1d7f723ede772c346c0dc4e0cb36464248 100644 (file)
@@ -19,3 +19,6 @@ This product contains the Piccolo XML Parser for Java
 
 This product contains the chunks_parse_cmds.tbl file from the vsdump program.
 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.
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/AbstractXmlSignatureService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/AbstractXmlSignatureService.java
new file mode 100644 (file)
index 0000000..73b4980
--- /dev/null
@@ -0,0 +1,610 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+/*
+ * 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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/KeyInfoKeySelector.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/KeyInfoKeySelector.java
new file mode 100644 (file)
index 0000000..fc55015
--- /dev/null
@@ -0,0 +1,99 @@
+
+/* ====================================================================
+   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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseInputStream.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseInputStream.java
new file mode 100644 (file)
index 0000000..abd3551
--- /dev/null
@@ -0,0 +1,53 @@
+
+/* ====================================================================
+   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");
+    }
+}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseOutputStream.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/NoCloseOutputStream.java
new file mode 100644 (file)
index 0000000..85464bf
--- /dev/null
@@ -0,0 +1,54 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+/*
+ * 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
+    }
+}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/SignatureAspect.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/SignatureAspect.java
new file mode 100644 (file)
index 0000000..9865a77
--- /dev/null
@@ -0,0 +1,56 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+/*
+ * 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;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/TemporaryDataStorage.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/TemporaryDataStorage.java
new file mode 100644 (file)
index 0000000..ec5c940
--- /dev/null
@@ -0,0 +1,65 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+/*
+ * 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);
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/AbstractOOXMLSignatureService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/AbstractOOXMLSignatureService.java
new file mode 100644 (file)
index 0000000..f76d69d
--- /dev/null
@@ -0,0 +1,348 @@
+
+/* ====================================================================
+   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 = KeyInfoFactory.getInstance();
+        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("generic error: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * The output stream to which to write the signed Office OpenXML file.
+     * 
+     * @return
+     */
+    abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream();
+
+    /**
+     * Gives back the URL of the OOXML to be signed.
+     * 
+     * @return
+     */
+    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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLProvider.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLProvider.java
new file mode 100644 (file)
index 0000000..16fe40c
--- /dev/null
@@ -0,0 +1,54 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+
+/*
+ * 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());
+        }
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureAspect.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureAspect.java
new file mode 100644 (file)
index 0000000..df69566
--- /dev/null
@@ -0,0 +1,353 @@
+
+/* ====================================================================
+   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.LinkedList;
+import java.util.List;
+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.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+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.
+ */
+public class OOXMLSignatureAspect implements SignatureAspect {
+
+    private static final Log LOG = LogFactory.getLog(OOXMLSignatureAspect.class);
+
+    private final AbstractOOXMLSignatureService signatureService;
+
+    /**
+     * Main constructor.
+     * 
+     * @param ooxmlUrl
+     */
+    public OOXMLSignatureAspect(AbstractOOXMLSignatureService signatureService) {
+        this.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, document);
+        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, Document document) throws NoSuchAlgorithmException,
+                                    InvalidAlgorithmParameterException {
+        List<Reference> manifestReferences = new LinkedList<Reference>();
+
+        try {
+            addRelationshipsReferences(signatureFactory, document, 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 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");
+        DateTime dateTime = new DateTime(DateTimeZone.UTC);
+        DateTimeFormatter fmt = ISODateTimeFormat.dateTimeNoMillis();
+        String now = fmt.print(dateTime);
+        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);
+    }
+
+    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, Document document, List<Reference> manifestReferences) throws IOException,
+                                    ParserConfigurationException, SAXException, TransformerException, NoSuchAlgorithmException,
+                                    InvalidAlgorithmParameterException {
+        URL ooxmlUrl = this.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, document, zipEntry.getName(), relsDocument, manifestReferences);
+        }
+    }
+
+    private void addRelationshipsReference(XMLSignatureFactory signatureFactory, Document document, 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(this.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 = this.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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureVerifier.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLSignatureVerifier.java
new file mode 100644 (file)
index 0000000..885b7f0
--- /dev/null
@@ -0,0 +1,211 @@
+
+/* ====================================================================
+   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 class OOXMLSignatureVerifier {
+
+    private static final Log LOG = LogFactory.getLog(OOXMLSignatureVerifier.class);
+
+    private OOXMLSignatureVerifier() {
+        super();
+    }
+
+    /**
+     * Checks whether the file referred by the given URL is an OOXML document.
+     * 
+     * @param url
+     * @return
+     * @throws IOException
+     */
+    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 = XMLSignatureFactory.getInstance();
+            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 = XMLSignatureFactory.getInstance();
+        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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLURIDereferencer.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/OOXMLURIDereferencer.java
new file mode 100644 (file)
index 0000000..d00f010
--- /dev/null
@@ -0,0 +1,111 @@
+
+/* ====================================================================
+   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 = XMLSignatureFactory.getInstance();
+        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.info("No part found for URI: " + uri);
+        return null;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipComparator.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipComparator.java
new file mode 100644 (file)
index 0000000..5ed6464
--- /dev/null
@@ -0,0 +1,41 @@
+
+/* ====================================================================
+   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);
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformParameterSpec.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformParameterSpec.java
new file mode 100644 (file)
index 0000000..c0bb648
--- /dev/null
@@ -0,0 +1,58 @@
+
+/* ====================================================================
+   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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/RelationshipTransformService.java
new file mode 100644 (file)
index 0000000..7f67bbf
--- /dev/null
@@ -0,0 +1,274 @@
+
+/* ====================================================================
+   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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java
new file mode 100644 (file)
index 0000000..eb7c767
--- /dev/null
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+   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;
+
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java
new file mode 100644 (file)
index 0000000..60ff0da
--- /dev/null
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+   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;
+
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java
new file mode 100644 (file)
index 0000000..4ed07ff
--- /dev/null
@@ -0,0 +1,56 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+
+/*
+ * 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;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java
new file mode 100644 (file)
index 0000000..1a2b6b7
--- /dev/null
@@ -0,0 +1,54 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+
+/*
+ * 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;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java
new file mode 100644 (file)
index 0000000..495bd57
--- /dev/null
@@ -0,0 +1,64 @@
+
+/* ====================================================================
+   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;
+    }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java
new file mode 100644 (file)
index 0000000..f285c23
--- /dev/null
@@ -0,0 +1,73 @@
+
+/* ====================================================================
+   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;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java
new file mode 100644 (file)
index 0000000..6b86b2f
--- /dev/null
@@ -0,0 +1,77 @@
+
+/* ====================================================================
+   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);
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java
new file mode 100644 (file)
index 0000000..1b4a89f
--- /dev/null
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+   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;
+
diff --git a/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx b/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx
new file mode 100644 (file)
index 0000000..5162b67
Binary files /dev/null and b/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx differ
diff --git a/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx b/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx
new file mode 100644 (file)
index 0000000..cbd4277
Binary files /dev/null and b/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed-twice.docx b/src/ooxml/testcases/hello-world-signed-twice.docx
new file mode 100644 (file)
index 0000000..96c91e9
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed-twice.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed.docx b/src/ooxml/testcases/hello-world-signed.docx
new file mode 100644 (file)
index 0000000..79a7bbb
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed.pptx b/src/ooxml/testcases/hello-world-signed.pptx
new file mode 100644 (file)
index 0000000..9b37033
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.pptx differ
diff --git a/src/ooxml/testcases/hello-world-signed.xlsx b/src/ooxml/testcases/hello-world-signed.xlsx
new file mode 100644 (file)
index 0000000..0d45c53
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.xlsx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.docx b/src/ooxml/testcases/hello-world-unsigned.docx
new file mode 100644 (file)
index 0000000..1790c96
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.docx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.pptx b/src/ooxml/testcases/hello-world-unsigned.pptx
new file mode 100644 (file)
index 0000000..ca42529
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.pptx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.xlsx b/src/ooxml/testcases/hello-world-unsigned.xlsx
new file mode 100644 (file)
index 0000000..b99143e
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.xlsx differ
diff --git a/src/ooxml/testcases/invalidsig.docx b/src/ooxml/testcases/invalidsig.docx
new file mode 100644 (file)
index 0000000..c448e81
Binary files /dev/null and b/src/ooxml/testcases/invalidsig.docx differ
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java
new file mode 100644 (file)
index 0000000..a307ee0
--- /dev/null
@@ -0,0 +1,199 @@
+
+/* ====================================================================
+   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 javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.bouncycastle.asn1.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.joda.time.DateTime;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class PkiTestUtils {
+
+    public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14 };
+
+    private PkiTestUtils() {
+        super();
+    }
+
+    static KeyPair generateKeyPair() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        SecureRandom random = new SecureRandom();
+        keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), random);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        return keyPair;
+    }
+
+    private static 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);
+    }
+
+    static X509Certificate generateCertificate(PublicKey subjectPublicKey, String subjectDn, DateTime notBefore, DateTime notAfter,
+                                    X509Certificate issuerCertificate, PrivateKey issuerPrivateKey, boolean caFlag, int pathLength, String crlUri,
+                                    String ocspUri, KeyUsage keyUsage) throws IOException, InvalidKeyException, IllegalStateException,
+                                    NoSuchAlgorithmException, SignatureException, CertificateException {
+        String signatureAlgorithm = "SHA1withRSA";
+        X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
+        certificateGenerator.reset();
+        certificateGenerator.setPublicKey(subjectPublicKey);
+        certificateGenerator.setSignatureAlgorithm(signatureAlgorithm);
+        certificateGenerator.setNotBefore(notBefore.toDate());
+        certificateGenerator.setNotAfter(notAfter.toDate());
+        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;
+    }
+
+    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();
+    }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java
new file mode 100644 (file)
index 0000000..83a36cc
--- /dev/null
@@ -0,0 +1,66 @@
+
+/* ====================================================================
+   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);
+    }
+}
\ No newline at end of file
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java
new file mode 100644 (file)
index 0000000..d6cc51c
--- /dev/null
@@ -0,0 +1,214 @@
+
+/* ====================================================================
+   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.TemporaryDataStorage;
+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;
+import org.joda.time.DateTime;
+
+
+
+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) {
+            this.temporaryDataStorage = new TemporaryTestDataStorage();
+            this.signedOOXMLOutputStream = new ByteArrayOutputStream();
+            this.ooxmlUrl = ooxmlUrl;
+        }
+
+        @Override
+        protected URL getOfficeOpenXMLDocumentURL() {
+            return this.ooxmlUrl;
+        }
+
+        @Override
+        protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream() {
+            return this.signedOOXMLOutputStream;
+        }
+
+        public byte[] getSignedOfficeOpenXMLDocumentData() {
+            return this.signedOOXMLOutputStream.toByteArray();
+        }
+
+        @Override
+        protected TemporaryDataStorage getTemporaryDataStorage() {
+            return this.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);
+
+        DateTime notBefore = new DateTime();
+        DateTime notAfter = notBefore.plusYears(1);
+        X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), signerDn, notBefore, notAfter, 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;
+    }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java
new file mode 100644 (file)
index 0000000..c1e474f
--- /dev/null
@@ -0,0 +1,560 @@
+
+/* ====================================================================
+   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.joda.time.DateTime;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+
+public 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;
+        }
+    }
+
+    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);
+        assertFalse(digestValueTextContent.isEmpty());
+
+        /*
+         * 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);
+
+        DateTime notBefore = new DateTime();
+        DateTime notAfter = notBefore.plusYears(1);
+        X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, 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 = XMLSignatureFactory.getInstance();
+        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);
+        assertFalse(digestValueTextContent.isEmpty());
+
+        /*
+         * 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);
+
+        DateTime notBefore = new DateTime();
+        DateTime notAfter = notBefore.plusYears(1);
+        X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, 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 = XMLSignatureFactory.getInstance();
+        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);
+        assertFalse(digestValueTextContent.isEmpty());
+
+        /*
+         * 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);
+
+        DateTime notBefore = new DateTime();
+        DateTime notAfter = notBefore.plusYears(1);
+        X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, 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 = XMLSignatureFactory.getInstance();
+        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);
+        assertFalse(digestValueTextContent.isEmpty());
+
+        /*
+         * 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);
+
+        DateTime notBefore = new DateTime();
+        DateTime notAfter = notBefore.plusYears(1);
+        X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, 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 = XMLSignatureFactory.getInstance();
+        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);
+        assertFalse(digestValueTextContent.isEmpty());
+    }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java
new file mode 100644 (file)
index 0000000..9aa79f3
--- /dev/null
@@ -0,0 +1,238 @@
+
+/* ====================================================================
+   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);
+    }
+}