]> source.dussan.org Git - poi.git/commitdiff
#62159 - Support XML signature over windows certificate store
authorAndreas Beeker <kiwiwings@apache.org>
Tue, 6 Mar 2018 00:07:20 +0000 (00:07 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Tue, 6 Mar 2018 00:07:20 +0000 (00:07 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1825948 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestInfo.java [deleted file]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestOutputStream.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureOutputStream.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
src/ooxml/testcases/org/apache/poi/poifs/crypt/dsig/TestSignatureConfig.java

index 0440c78c04af26326b8fc4a8c9a529d01005ecba..b4618dc0b5a406ef055cbd7e3fe20957e89d3496 100644 (file)
@@ -20,55 +20,72 @@ package org.apache.poi.poifs.crypt;
 import org.apache.poi.EncryptedDocumentException;
 
 public enum HashAlgorithm {
-    none     (         "", 0x0000,           "",  0,               "", false),
-    sha1     (    "SHA-1", 0x8004,       "SHA1", 20,       "HmacSHA1", false),
-    sha256   (  "SHA-256", 0x800C,     "SHA256", 32,     "HmacSHA256", false),
-    sha384   (  "SHA-384", 0x800D,     "SHA384", 48,     "HmacSHA384", false),
-    sha512   (  "SHA-512", 0x800E,     "SHA512", 64,     "HmacSHA512", false),
+    none     (         "", 0x0000,           "",  0,               "", false, ""),
+    sha1     (    "SHA-1", 0x8004,       "SHA1", 20,       "HmacSHA1", false, "1.3.14.3.2.26"),
+    sha256   (  "SHA-256", 0x800C,     "SHA256", 32,     "HmacSHA256", false, "2.16.840.1.101.3.4.2.1"),
+    sha384   (  "SHA-384", 0x800D,     "SHA384", 48,     "HmacSHA384", false, "2.16.840.1.101.3.4.2.2"),
+    sha512   (  "SHA-512", 0x800E,     "SHA512", 64,     "HmacSHA512", false, "2.16.840.1.101.3.4.2.3"),
     /* only for agile encryption */
-    md5      (      "MD5",     -1,        "MD5", 16,        "HmacMD5", false),
+    md5      (      "MD5",     -1,        "MD5", 16,        "HmacMD5", false, "1.2.840.113549.2.5" ),
     // although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle
-    md2      (      "MD2",     -1,        "MD2", 16,       "Hmac-MD2", true),
-    md4      (      "MD4",     -1,        "MD4", 16,       "Hmac-MD4", true),
-    ripemd128("RipeMD128",     -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
-    ripemd160("RipeMD160",     -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
-    whirlpool("Whirlpool",     -1,  "WHIRLPOOL", 64, "HMac-Whirlpool", true),
+    md2      (      "MD2",     -1,        "MD2", 16,       "Hmac-MD2", true, "1.2.840.113549.2.2" ),
+    md4      (      "MD4",     -1,        "MD4", 16,       "Hmac-MD4", true, "1.2.840.113549.2.4" ),
+    ripemd128("RipeMD128",     -1, "RIPEMD-128", 16, "HMac-RipeMD128", true, "1.3.36.3.2.2"),
+    ripemd160("RipeMD160",     -1, "RIPEMD-160", 20, "HMac-RipeMD160", true, "1.3.36.3.2.1"),
+    whirlpool("Whirlpool",     -1,  "WHIRLPOOL", 64, "HMac-Whirlpool", true, "1.0.10118.3.0.55"),
     // only for xml signing
-    sha224   (  "SHA-224",     -1,     "SHA224", 28,     "HmacSHA224", true);
+    sha224   (  "SHA-224",     -1,     "SHA224", 28,     "HmacSHA224", true, "2.16.840.1.101.3.4.2.4"),
+    ripemd256("RipeMD256",     -1, "RIPEMD-256", 32, "HMac-RipeMD256", true, "1.3.36.3.2.3")
+    ;
 
-    public final String jceId;
-    public final int ecmaId;
+    /** the id used for initializing the JCE message digest **/
+       public final String jceId;
+       /** the id used for the BIFF encryption info header **/
+       public final int ecmaId;
+       /** the id used for OOXML encryption info header **/ 
     public final String ecmaString;
+    /** the length of the digest byte array **/
     public final int hashSize;
+    /** the id used for the integrity algorithm in agile encryption **/
     public final String jceHmacId;
+    /** is bouncycastle necessary for calculating the digest **/ 
     public final boolean needsBouncyCastle;
+    /** ASN1 object identifier of the digest value in combination with the RSA cipher */  
+    public final String rsaOid;
     
-    HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) {
+    HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle, String rsaOid) {
         this.jceId = jceId;
         this.ecmaId = ecmaId;
         this.ecmaString = ecmaString;
         this.hashSize = hashSize;
         this.jceHmacId = jceHmacId;
         this.needsBouncyCastle = needsBouncyCastle;
+        this.rsaOid = rsaOid;
     }
     
     public static HashAlgorithm fromEcmaId(int ecmaId) {
         for (HashAlgorithm ha : values()) {
-            if (ha.ecmaId == ecmaId) return ha;
+            if (ha.ecmaId == ecmaId) {
+                return ha;
+            }
         }
         throw new EncryptedDocumentException("hash algorithm not found");
     }    
     
     public static HashAlgorithm fromEcmaId(String ecmaString) {
         for (HashAlgorithm ha : values()) {
-            if (ha.ecmaString.equals(ecmaString)) return ha;
+            if (ha.ecmaString.equals(ecmaString)) {
+                return ha;
+            }
         }
         throw new EncryptedDocumentException("hash algorithm not found");
     }
     
     public static HashAlgorithm fromString(String string) {
         for (HashAlgorithm ha : values()) {
-            if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) return ha;
+            if (ha.ecmaString.equalsIgnoreCase(string) || ha.jceId.equalsIgnoreCase(string)) {
+                return ha;
+            }
         }
         throw new EncryptedDocumentException("hash algorithm not found");
     }
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestInfo.java
deleted file mode 100644 (file)
index 3771053..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/* ====================================================================
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================== */
-
-/* ====================================================================
-   This product contains an ASLv2 licensed version of the OOXML signer
-   package from the eID Applet project
-   http://code.google.com/p/eid-applet/source/browse/trunk/README.txt  
-   Copyright (C) 2008-2014 FedICT.
-   ================================================================= */ 
-
-package org.apache.poi.poifs.crypt.dsig;
-
-import java.io.Serializable;
-
-import org.apache.poi.poifs.crypt.HashAlgorithm;
-
-/**
- * Digest Information data transfer class.
- */
-public class DigestInfo implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * Main constructor.
-     * 
-     * @param digestValue
-     * @param hashAlgo
-     * @param description
-     */
-    public DigestInfo(byte[] digestValue, HashAlgorithm hashAlgo, String description) {
-        this.digestValue = digestValue.clone();
-        this.hashAlgo = hashAlgo;
-        this.description = description;
-    }
-
-    public final byte[] digestValue;
-
-    public final String description;
-
-    public final HashAlgorithm hashAlgo;
-}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestOutputStream.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/DigestOutputStream.java
new file mode 100644 (file)
index 0000000..f8ad4a3
--- /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.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+
+import javax.crypto.Cipher;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
+/* package */ class DigestOutputStream extends OutputStream {
+    final HashAlgorithm algo;
+    final PrivateKey key;
+    private MessageDigest md;
+
+    DigestOutputStream(final HashAlgorithm algo, final PrivateKey key) {
+        this.algo = algo;
+        this.key = key;
+    }
+
+    public void init() throws GeneralSecurityException {
+        if (isMSCapi(key)) {
+            // see https://stackoverflow.com/questions/39196145 for problems with SunMSCAPI
+            // and why we can't sign the calculated digest
+            throw new EncryptedDocumentException(
+                "Windows keystore entries can't be signed with the "+algo+" hash. Please "+
+                "use one digest algorithm of sha1 / sha256 / sha384 / sha512.");
+        }
+        md = CryptoFunctions.getMessageDigest(algo);
+    }
+    
+    @Override
+    public void write(final int b) throws IOException {
+        md.update((byte)b);
+    }
+
+    @Override
+    public void write(final byte[] data, final int off, final int len) throws IOException {
+        md.update(data, off, len);
+    }
+
+    public byte[] sign() throws IOException, GeneralSecurityException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        bos.write(getHashMagic());
+        bos.write(md.digest());
+
+        final Cipher cipher = CryptoFunctions.getCipher(key, CipherAlgorithm.rsa
+            , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
+        return cipher.doFinal(bos.toByteArray());
+    }
+    
+    static boolean isMSCapi(final PrivateKey key) {
+        return key != null && key.getClass().getName().contains("mscapi");
+    }
+
+
+    /**
+     * Each digest method has its own ASN1 header
+     *
+     * @return the ASN1 header bytes for the signatureValue / digestInfo
+     * 
+     * @see <a href="https://tools.ietf.org/html/rfc2313#section-10.1.2">Data encoding</a>
+     */
+    byte[] getHashMagic() {
+        // in an earlier release the hashMagic (aka DigestAlgorithmIdentifier) contained only
+        // an object identifier, but to conform with the header generated by the
+        // javax-signature API, the empty <associated parameters> are also included
+        try {
+            final byte[] oidBytes = new Oid(algo.rsaOid).getDER();
+
+            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            bos.write(0x30);
+            bos.write(algo.hashSize+oidBytes.length+6);
+            bos.write(0x30);
+            bos.write(oidBytes.length+2);
+            bos.write(oidBytes);
+            bos.write(new byte[] {5,0,4});
+            bos.write(algo.hashSize);
+            
+            return bos.toByteArray();
+        } catch (GSSException|IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
index 85df30b3fdc570406a0c07d591db07166e24291a..f3184ab09fcc43e21c390e15e9ed5285a80ee456 100644 (file)
@@ -186,7 +186,9 @@ public class SignatureConfig {
             namespacePrefixes.put(XADES_132_NS, "xd");
         }
         
-        if (onlyValidation) return;
+        if (onlyValidation) {
+            return;
+        }
 
         if (signatureMarshalListener == null) {
             signatureMarshalListener = new SignatureMarshalListener();
@@ -711,55 +713,6 @@ public class SignatureConfig {
         return value == null ? defaultValue : value;
     }
 
-    /**
-     * Each digest method has its own IV (initial vector)
-     *
-     * @return the IV depending on the main digest method
-     */
-    public byte[] getHashMagic() {
-        // see https://www.ietf.org/rfc/rfc3110.txt
-        // RSA/SHA1 SIG Resource Records
-        byte result[];
-        switch (getDigestAlgo()) {
-        case sha1: result = new byte[]
-            { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e
-            , 0x03, 0x02, 0x1a, 0x04, 0x14 };
-            break;
-        case sha224: result = new byte[] 
-            { 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
-            , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c };
-            break;
-        case sha256: result = new byte[]
-            { 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
-            , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
-            break;
-        case sha384: result = new byte[]
-            { 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
-            , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 };
-            break;
-        case sha512: result  = new byte[]
-            { 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
-            , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 };
-            break;
-        case ripemd128: result = new byte[]
-            { 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
-            , 0x03, 0x02, 0x02, 0x04, 0x10 };
-            break;
-        case ripemd160: result = new byte[]
-            { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
-            , 0x03, 0x02, 0x01, 0x04, 0x14 };
-            break;
-        // case ripemd256: result = new byte[]
-        //    { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
-        //    , 0x03, 0x02, 0x03, 0x04, 0x20 };
-        //    break;
-        default: throw new EncryptedDocumentException("Hash algorithm "
-            +getDigestAlgo()+" not supported for signing.");
-        }
-        
-        return result;
-    }
-
     /**
      * @return the uri for the signature method, i.e. currently only rsa is
      * supported, so it's the rsa variant of the main digest
@@ -785,7 +738,10 @@ public class SignatureConfig {
     }
     
     /**
-     * @param digestAlgo the digest algo, currently only sha* and ripemd160 is supported 
+     * Sets the digest algorithm - currently only sha* and ripemd160 is supported.
+     * MS Office only supports sha1, sha256, sha384, sha512. 
+     * 
+     * @param digestAlgo the digest algorithm  
      * @return the uri for the given digest
      */
     public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
@@ -857,11 +813,15 @@ public class SignatureConfig {
         if (prov == null) {
             String dsigProviderNames[] = {
                 System.getProperty("jsr105Provider"),
-                "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI", // Santuario xmlsec
-                "org.jcp.xml.dsig.internal.dom.XMLDSigRI"         // JDK xmlsec
+                // Santuario xmlsec
+                "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI",
+                // JDK xmlsec
+                "org.jcp.xml.dsig.internal.dom.XMLDSigRI"         
             };
             for (String pn : dsigProviderNames) {
-                if (pn == null) continue;
+                if (pn == null) {
+                    continue;
+                }
                 try {
                     prov = (Provider)Class.forName(pn).newInstance();
                     break;
index 9ea11047009e258a98967495658fa71ae0a3eba1..7c794071cfae9ebfcfaeb5ce8befc5f75bdd490c 100644 (file)
 /* ====================================================================
    This product contains an ASLv2 licensed version of the OOXML signer
    package from the eID Applet project
-   http://code.google.com/p/eid-applet/source/browse/trunk/README.txt  
+   http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
    Copyright (C) 2008-2014 FedICT.
-   ================================================================= */ 
+   ================================================================= */
 
 package org.apache.poi.poifs.crypt.dsig;
 
 import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
 import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
 
-import javax.crypto.Cipher;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.xml.bind.DatatypeConverter;
 import javax.xml.crypto.MarshalException;
 import javax.xml.crypto.URIDereferencer;
 import javax.xml.crypto.XMLStructure;
@@ -36,38 +47,16 @@ 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.TransformException;
 import javax.xml.crypto.dsig.XMLObject;
-import javax.xml.crypto.dsig.XMLSignContext;
-import javax.xml.crypto.dsig.XMLSignature;
 import javax.xml.crypto.dsig.XMLSignatureException;
 import javax.xml.crypto.dsig.XMLSignatureFactory;
-import javax.xml.crypto.dsig.XMLValidateContext;
 import javax.xml.crypto.dsig.dom.DOMSignContext;
-import javax.xml.crypto.dsig.dom.DOMValidateContext;
 import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.Provider;
-import java.security.Security;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
 
 import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
 import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
+import org.apache.jcp.xml.dsig.internal.dom.DOMSubTreeData;
 import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.opc.ContentTypes;
@@ -79,9 +68,8 @@ import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
 import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
 import org.apache.poi.openxml4j.opc.PackagingURIHelper;
 import org.apache.poi.openxml4j.opc.TargetMode;
-import org.apache.poi.poifs.crypt.ChainingMode;
-import org.apache.poi.poifs.crypt.CipherAlgorithm;
 import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
 import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
 import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
@@ -90,7 +78,7 @@ import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.xml.security.Init;
 import org.apache.xml.security.utils.Base64;
-import org.apache.xmlbeans.XmlException;
+import org.apache.xml.security.utils.XMLUtils;
 import org.apache.xmlbeans.XmlOptions;
 import org.w3.x2000.x09.xmldsig.SignatureDocument;
 import org.w3c.dom.Document;
@@ -98,15 +86,14 @@ import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.events.EventListener;
 import org.w3c.dom.events.EventTarget;
-import org.xml.sax.SAXException;
 
 
 /**
  * <p>This class is the default entry point for XML signatures and can be used for
  * validating an existing signed office document and signing a office document.</p>
- * 
+ *
  * <p><b>Validating a signed office document</b></p>
- * 
+ *
  * <pre>
  * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ);
  * SignatureConfig sic = new SignatureConfig();
@@ -116,9 +103,9 @@ import org.xml.sax.SAXException;
  * boolean isValid = si.validate();
  * ...
  * </pre>
- * 
+ *
  * <p><b>Signing an office document</b></p>
- * 
+ *
  * <pre>
  * // loading the keystore - pkcs12 is used here, but of course jks &amp; co are also valid
  * // the keystore needs to contain a private key and it's certificate having a
@@ -129,19 +116,19 @@ import org.xml.sax.SAXException;
  * FileInputStream fis = new FileInputStream(file);
  * keystore.load(fis, password);
  * fis.close();
- * 
+ *
  * // extracting private key and certificate
  * String alias = "xyz"; // alias of the keystore entry
  * Key key = keystore.getKey(alias, password);
  * X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);
- * 
+ *
  * // filling the SignatureConfig entries (minimum fields, more options are available ...)
  * SignatureConfig signatureConfig = new SignatureConfig();
  * signatureConfig.setKey(keyPair.getPrivate());
  * signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
  * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);
  * signatureConfig.setOpcPackage(pkg);
- * 
+ *
  * // adding the signature document to the package
  * SignatureInfo si = new SignatureInfo();
  * si.setSignatureConfig(signatureConfig);
@@ -152,14 +139,14 @@ import org.xml.sax.SAXException;
  * // write the changes back to disc
  * pkg.close();
  * </pre>
- * 
+ *
  * <p><b>Implementation notes:</b></p>
- * 
+ *
  * <p>Although there's a XML signature implementation in the Oracle JDKs 6 and higher,
  * compatibility with IBM JDKs is also in focus (... but maybe not thoroughly tested ...).
  * Therefore we are using the Apache Santuario libs (xmlsec) instead of the built-in classes,
  * as the compatibility seems to be provided there.</p>
- * 
+ *
  * <p>To use SignatureInfo and its sibling classes, you'll need to have the following libs
  * in the classpath:</p>
  * <ul>
@@ -172,130 +159,17 @@ public class SignatureInfo implements SignatureConfigurable {
 
     private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
     private static boolean isInitialized;
-    
+
     private SignatureConfig signatureConfig;
 
-    public class SignaturePart {
-        private final PackagePart signaturePart;
-        private X509Certificate signer;
-        private List<X509Certificate> certChain;
-        
-        private SignaturePart(PackagePart signaturePart) {
-            this.signaturePart = signaturePart;
-        }
-        
-        /**
-         * @return the package part containing the signature
-         */
-        public PackagePart getPackagePart() {
-            return signaturePart;
-        }
-        
-        /**
-         * @return the signer certificate
-         */
-        public X509Certificate getSigner() {
-            return signer;
-        }
-        
-        /**
-         * @return the certificate chain of the signer
-         */
-        public List<X509Certificate> getCertChain() {
-            return certChain;
-        }
-        
-        /**
-         * Helper method for examining the xml signature
-         *
-         * @return the xml signature document
-         * @throws IOException if the xml signature doesn't exist or can't be read
-         * @throws XmlException if the xml signature is malformed
-         */
-        public SignatureDocument getSignatureDocument() throws IOException, XmlException {
-            // TODO: check for XXE
-            return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
-        }
-        
-        /**
-         * @return true, when the xml signature is valid, false otherwise
-         * 
-         * @throws EncryptedDocumentException if the signature can't be extracted or if its malformed
-         */
-        @SuppressWarnings("unchecked")
-        public boolean validate() {
-            KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
-            try {
-                Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
-                XPath xpath = XPathFactory.newInstance().newXPath();
-                NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
-                final int length = nl.getLength();
-                for (int i=0; i<length; i++) {
-                    ((Element)nl.item(i)).setIdAttribute("Id", true);
-                }
-                
-                DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
-                domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
-                domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
-                brokenJvmWorkaround(domValidateContext);
-    
-                XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();
-                XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
-                
-                // TODO: replace with property when xml-sec patch is applied
-                // workaround added in r1637283 2014-11-07
-                for (Reference ref : (List<Reference>)xmlSignature.getSignedInfo().getReferences()) {
-                    SignatureFacet.brokenJvmWorkaround(ref);
-                }
-                for (XMLObject xo : (List<XMLObject>)xmlSignature.getObjects()) {
-                    for (XMLStructure xs : (List<XMLStructure>)xo.getContent()) {
-                        if (xs instanceof Manifest) {
-                           for (Reference ref : (List<Reference>)((Manifest)xs).getReferences()) {
-                               SignatureFacet.brokenJvmWorkaround(ref);
-                           }
-                        }
-                    }
-                }
-                
-                boolean valid = xmlSignature.validate(domValidateContext);
 
-                if (valid) {
-                    signer = keySelector.getSigner();
-                    certChain = keySelector.getCertChain();
-                }
-                
-                return valid;
-            } catch (IOException e) {
-                String s = "error in reading document";
-                LOG.log(POILogger.ERROR, s, e);
-                throw new EncryptedDocumentException(s, e);
-            } catch (SAXException e) {
-                String s = "error in parsing document";
-                LOG.log(POILogger.ERROR, s, e);
-                throw new EncryptedDocumentException(s, e);
-            } catch (XPathExpressionException e) {
-                String s = "error in searching document with xpath expression";
-                LOG.log(POILogger.ERROR, s, e);
-                throw new EncryptedDocumentException(s, e);
-            } catch (MarshalException e) {
-                String s = "error in unmarshalling the signature";
-                LOG.log(POILogger.ERROR, s, e);
-                throw new EncryptedDocumentException(s, e);
-            } catch (XMLSignatureException e) {
-                String s = "error in validating the signature";
-                LOG.log(POILogger.ERROR, s, e);
-                throw new EncryptedDocumentException(s, e);
-            }
-        }
-    }
-    
     /**
      * Constructor initializes xml signature environment, if it hasn't been initialized before
      */
     public SignatureInfo() {
-        initXmlProvider();        
+        initXmlProvider();
     }
-    
+
     /**
      * @return the signature config
      */
@@ -306,6 +180,7 @@ public class SignatureInfo implements SignatureConfigurable {
     /**
      * @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used
      */
+    @Override
     public void setSignatureConfig(SignatureConfig signatureConfig) {
         this.signatureConfig = signatureConfig;
     }
@@ -329,18 +204,31 @@ public class SignatureInfo implements SignatureConfigurable {
      * @throws MarshalException
      */
     public void confirmSignature() throws XMLSignatureException, MarshalException {
-        Document document = DocumentHelper.createDocument();
-        
+        final Document document = DocumentHelper.createDocument();
+        final DOMSignContext xmlSignContext = createXMLSignContext(document);
+
         // operate
-        DigestInfo digestInfo = preSign(document, null);
+        final DOMSignedInfo signedInfo = preSign(xmlSignContext);
 
         // setup: key material, signature value
-        byte[] signatureValue = signDigest(digestInfo.digestValue);
-        
+        final String signatureValue = signDigest(xmlSignContext, signedInfo);
+
         // operate: postSign
-        postSign(document, signatureValue);
+        postSign(xmlSignContext, signatureValue);
     }
 
+    /**
+     * Convenience method for creating the signature context
+     *
+     * @param document the document the signature is based on
+     *
+     * @return the initialized signature context
+     */
+    public DOMSignContext createXMLSignContext(final Document document) {
+        return new DOMSignContext(signatureConfig.getKey(), document);
+    }
+
+
     /**
      * Sign (encrypt) the digest with the private key.
      * Currently only rsa is supported.
@@ -348,21 +236,40 @@ public class SignatureInfo implements SignatureConfigurable {
      * @param digest the hashed input
      * @return the encrypted hash
      */
-    public byte[] signDigest(byte digest[]) {
-        Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa
-            , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
-            
+    public String signDigest(final DOMSignContext xmlSignContext, final DOMSignedInfo signedInfo) {
+        final PrivateKey key = signatureConfig.getKey();
+        final HashAlgorithm algo = signatureConfig.getDigestAlgo();
+
+        if (algo.hashSize*4/3 > Base64.BASE64DEFAULTLENGTH && !XMLUtils.ignoreLineBreaks()) {
+            throw new EncryptedDocumentException("The hash size of the choosen hash algorithm ("+algo+" = "+algo.hashSize+" bytes), "+
+                "will motivate XmlSec to add linebreaks to the generated digest, which results in an invalid signature (... at least "+
+                "for Office) - please persuade it otherwise by adding '-Dorg.apache.xml.security.ignoreLineBreaks=true' to the JVM "+
+                "system properties.");
+        }
+        
         try {
-            ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
-            digestInfoValueBuf.write(signatureConfig.getHashMagic());
-            digestInfoValueBuf.write(digest);
-            byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
-            return cipher.doFinal(digestInfoValue);
-        } catch (Exception e) {
+            final DigestOutputStream dos;
+            switch (algo) {
+                case md2: case md5: case sha1: case sha256: case sha384: case sha512:
+                    dos = new SignatureOutputStream(algo, key);
+                    break;
+                default:
+                    dos = new DigestOutputStream(algo, key);
+                    break;
+            }
+            dos.init();
+
+            final Document document = (Document)xmlSignContext.getParent();
+            final Element el = getDsigElement(document, "SignedInfo");
+            final DOMSubTreeData subTree = new DOMSubTreeData(el, true);
+            signedInfo.getCanonicalizationMethod().transform(subTree, xmlSignContext, dos);
+
+            return DatatypeConverter.printBase64Binary(dos.sign());
+        } catch (GeneralSecurityException|IOException|TransformException e) {
             throw new EncryptedDocumentException(e);
         }
     }
-    
+
     /**
      * @return a signature part for each signature document.
      * the parts can be validated independently.
@@ -370,17 +277,21 @@ public class SignatureInfo implements SignatureConfigurable {
     public Iterable<SignaturePart> getSignatureParts() {
         signatureConfig.init(true);
         return new Iterable<SignaturePart>() {
+            @Override
             public Iterator<SignaturePart> iterator() {
                 return new Iterator<SignaturePart>() {
                     OPCPackage pkg = signatureConfig.getOpcPackage();
-                    Iterator<PackageRelationship> sigOrigRels = 
+                    Iterator<PackageRelationship> sigOrigRels =
                         pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator();
                     Iterator<PackageRelationship> sigRels;
                     PackagePart sigPart;
-                    
+
+                    @Override
                     public boolean hasNext() {
                         while (sigRels == null || !sigRels.hasNext()) {
-                            if (!sigOrigRels.hasNext()) return false;
+                            if (!sigOrigRels.hasNext()) {
+                                return false;
+                            }
                             sigPart = pkg.getPart(sigOrigRels.next());
                             LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
                             try {
@@ -391,21 +302,25 @@ public class SignatureInfo implements SignatureConfigurable {
                         }
                         return true;
                     }
-                    
+
+                    @Override
                     public SignaturePart next() {
                         PackagePart sigRelPart = null;
                         do {
                             try {
-                                if (!hasNext()) throw new NoSuchElementException();
-                                sigRelPart = sigPart.getRelatedPart(sigRels.next()); 
+                                if (!hasNext()) {
+                                    throw new NoSuchElementException();
+                                }
+                                sigRelPart = sigPart.getRelatedPart(sigRels.next());
                                 LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
                             } catch (InvalidFormatException e) {
                                 LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
                             }
                         } while (sigPart == null);
-                        return new SignaturePart(sigRelPart);
+                        return new SignaturePart(sigRelPart, signatureConfig);
                     }
-                    
+
+                    @Override
                     public void remove() {
                         throw new UnsupportedOperationException();
                     }
@@ -413,14 +328,16 @@ public class SignatureInfo implements SignatureConfigurable {
             }
         };
     }
-    
+
     /**
-     * Initialize the xml signing environment and the bouncycastle provider 
+     * Initialize the xml signing environment and the bouncycastle provider
      */
     protected static synchronized void initXmlProvider() {
-        if (isInitialized) return;
+        if (isInitialized) {
+            return;
+        }
         isInitialized = true;
-        
+
         try {
             Init.init();
             RelationshipTransformService.registerDsigProvider();
@@ -429,16 +346,18 @@ public class SignatureInfo implements SignatureConfigurable {
             throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
         }
     }
-    
+
     /**
      * Helper method for adding informations before the signing.
      * Normally {@link #confirmSignature()} is sufficient to be used.
      */
     @SuppressWarnings("unchecked")
-    public DigestInfo preSign(Document document, List<DigestInfo> digestInfos)
+    public DOMSignedInfo preSign(final DOMSignContext xmlSignContext)
     throws XMLSignatureException, MarshalException {
         signatureConfig.init(false);
-        
+
+        final Document document = (Document)xmlSignContext.getParent();
+
         // it's necessary to explicitly set the mdssi namespace, but the sign() method has no
         // normal way to interfere with, so we need to add the namespace under the hand ...
         EventTarget target = (EventTarget)document;
@@ -449,11 +368,10 @@ public class SignatureInfo implements SignatureConfigurable {
             }
             SignatureMarshalListener.setListener(target, creationListener, true);
         }
-        
+
         /*
          * Signature context construction.
          */
-        XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
         URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
         if (null != uriDereferencer) {
             xmlSignContext.setURIDereferencer(uriDereferencer);
@@ -464,23 +382,13 @@ public class SignatureInfo implements SignatureConfigurable {
         }
         xmlSignContext.setDefaultNamespacePrefix("");
         // signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));
-        
-        brokenJvmWorkaround(xmlSignContext);
-        
+
         XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory();
 
         /*
          * Add ds:References that come from signing client local files.
          */
         List<Reference> references = new ArrayList<>();
-        for (DigestInfo digestInfo : safe(digestInfos)) {
-            byte[] documentDigestValue = digestInfo.digestValue;
-
-            String uri = new File(digestInfo.description).getName();
-            Reference reference = SignatureFacet.newReference
-                (uri, null, null, null, documentDigestValue, signatureConfig);
-            references.add(reference);
-        }
 
         /*
          * Invoke the signature facets.
@@ -528,11 +436,15 @@ public class SignatureInfo implements SignatureConfigurable {
             List<XMLStructure> objectContentList = object.getContent();
             for (XMLStructure objectContent : objectContentList) {
                 LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());
-                if (!(objectContent instanceof Manifest)) continue;
+                if (!(objectContent instanceof Manifest)) {
+                    continue;
+                }
                 Manifest manifest = (Manifest) objectContent;
                 List<Reference> manifestReferences = manifest.getReferences();
                 for (Reference manifestReference : manifestReferences) {
-                    if (manifestReference.getDigestValue() != null) continue;
+                    if (manifestReference.getDigestValue() != null) {
+                        continue;
+                    }
 
                     DOMReference manifestDOMReference = (DOMReference)manifestReference;
                     manifestDOMReference.digest(xmlSignContext);
@@ -548,40 +460,26 @@ public class SignatureInfo implements SignatureConfigurable {
             DOMReference domReference = (DOMReference)signedInfoReference;
 
             // ds:Reference with external digest value
-            if (domReference.getDigestValue() != null) continue;
-            
+            if (domReference.getDigestValue() != null) {
+                continue;
+            }
+
             domReference.digest(xmlSignContext);
         }
 
-        /*
-         * Calculation of XML signature digest value.
-         */
-        DOMSignedInfo domSignedInfo = (DOMSignedInfo)signedInfo;
-        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
-        domSignedInfo.canonicalize(xmlSignContext, dataStream);
-        byte[] octets = dataStream.toByteArray();
-
-        /*
-         * TODO: we could be using DigestOutputStream here to optimize memory
-         * usage.
-         */
-
-        MessageDigest md = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo());
-        byte[] digestValue = md.digest(octets);
-        
-        
-        String description = signatureConfig.getSignatureDescription();
-        return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description);
+        return (DOMSignedInfo)signedInfo;
     }
 
     /**
      * Helper method for adding informations after the signing.
      * Normally {@link #confirmSignature()} is sufficient to be used.
      */
-    public void postSign(Document document, byte[] signatureValue)
+    public void postSign(final DOMSignContext xmlSignContext, final String signatureValue)
     throws MarshalException {
         LOG.log(POILogger.DEBUG, "postSign");
 
+        final Document document = (Document)xmlSignContext.getParent();
+
         /*
          * Check ds:Signature node.
          */
@@ -593,11 +491,11 @@ public class SignatureInfo implements SignatureConfigurable {
         /*
          * Insert signature value into the ds:SignatureValue element
          */
-        NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
-        if (sigValNl.getLength() != 1) {
+        final Element signatureNode = getDsigElement(document, "SignatureValue"); 
+        if (signatureNode == null) {
             throw new RuntimeException("preSign has to be called before postSign");
         }
-        sigValNl.item(0).setTextContent(Base64.encode(signatureValue));
+        signatureNode.setTextContent(signatureValue);
 
         /*
          * Allow signature facets to inject their own stuff.
@@ -620,7 +518,7 @@ public class SignatureInfo implements SignatureConfigurable {
         Map<String,String> namespaceMap = new HashMap<>();
         for(Map.Entry<String,String> entry : signatureConfig.getNamespacePrefixes().entrySet()){
             namespaceMap.put(entry.getValue(), entry.getKey());
-        }        
+        }
         xo.setSaveSuggestedPrefixes(namespaceMap);
         xo.setUseDefaultNamespace();
 
@@ -641,12 +539,12 @@ public class SignatureInfo implements SignatureConfigurable {
         } catch (InvalidFormatException e) {
             throw new MarshalException(e);
         }
-        
+
         PackagePart sigPart = pkg.getPart(sigPartName);
         if (sigPart == null) {
             sigPart = pkg.createPart(sigPartName, ContentTypes.DIGITAL_SIGNATURE_XML_SIGNATURE_PART);
         }
-        
+
         try {
             OutputStream os = sigPart.getOutputStream();
             SignatureDocument sigDoc = SignatureDocument.Factory.parse(document, DEFAULT_XML_OPTIONS);
@@ -655,46 +553,30 @@ public class SignatureInfo implements SignatureConfigurable {
         } catch (Exception e) {
             throw new MarshalException("Unable to write signature document", e);
         }
-        
+
         PackagePart sigsPart = pkg.getPart(sigsPartName);
         if (sigsPart == null) {
             // touch empty marker file
             sigsPart = pkg.createPart(sigsPartName, ContentTypes.DIGITAL_SIGNATURE_ORIGIN_PART);
         }
-        
+
         PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
         for (PackageRelationship pr : relCol) {
             pkg.removeRelationship(pr.getId());
         }
         pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
-        
+
         sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
     }
-    
-    /**
-     * Helper method for null lists, which are converted to empty lists
-     *
-     * @param other the reference to wrap, if null
-     * @return if other is null, an empty lists is returned, otherwise other is returned
-     */
-    private static <T> List<T> safe(List<T> other) {
-        List<T> emptyList = Collections.emptyList();
-        return other == null ? emptyList : other;
-    }
 
-    private void brokenJvmWorkaround(XMLSignContext context) {
-        // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
-        Provider bcProv = Security.getProvider("BC");
-        if (bcProv != null) {
-            context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
-        }        
-    }
+    private Element getDsigElement(final Document document, final String localName) {
+        NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, localName);
+        if (sigValNl.getLength() == 1) {
+            return (Element)sigValNl.item(0);
+        }
 
-    private void brokenJvmWorkaround(XMLValidateContext context) {
-        // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
-        Provider bcProv = Security.getProvider("BC");
-        if (bcProv != null) {
-            context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
-        }        
+        LOG.log(POILogger.WARN, "Signature element '"+localName+"' was "+(sigValNl.getLength() == 0 ? "not found" : "multiple times"));
+        
+        return null;
     }
 }
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureOutputStream.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureOutputStream.java
new file mode 100644 (file)
index 0000000..0a00e29
--- /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.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+
+/* package */ class SignatureOutputStream extends DigestOutputStream {
+    Signature signature;
+    
+    SignatureOutputStream(final HashAlgorithm algo, PrivateKey key) {
+        super(algo, key);
+    }
+    
+    @Override
+    public void init() throws GeneralSecurityException {
+        final String provider = isMSCapi(key) ? "SunMSCAPI" : "SunRsaSign";
+        signature = Signature.getInstance(algo.ecmaString+"withRSA", provider);
+        signature.initSign(key);
+    }
+
+    @Override
+    public byte[] sign() throws SignatureException {
+        return signature.sign();
+    }
+    
+
+    @Override
+    public void write(final int b) throws IOException {
+        try {
+            signature.update((byte)b);
+        } catch (final SignatureException e) {
+            throw new IOException(e);
+        }
+    }
+
+    @Override
+    public void write(final byte[] data, final int off, final int len) throws IOException {
+        try {
+            signature.update(data, off, len);
+        } catch (final SignatureException e) {
+            throw new IOException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java
new file mode 100644 (file)
index 0000000..696e7ee
--- /dev/null
@@ -0,0 +1,149 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+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.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.util.DocumentHelper;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlException;
+import org.w3.x2000.x09.xmldsig.SignatureDocument;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class SignaturePart {
+    private static final POILogger LOG = POILogFactory.getLogger(SignaturePart.class);
+    private static final String XMLSEC_VALIDATE_MANIFEST = "org.jcp.xml.dsig.validateManifests"; 
+
+    
+    private final PackagePart signaturePart;
+    private final SignatureConfig signatureConfig;
+    private X509Certificate signer;
+    private List<X509Certificate> certChain;
+    
+    /* package */ SignaturePart(final PackagePart signaturePart, final SignatureConfig signatureConfig) {
+        this.signaturePart = signaturePart;
+        this.signatureConfig = signatureConfig;
+    }
+    
+    /**
+     * @return the package part containing the signature
+     */
+    public PackagePart getPackagePart() {
+        return signaturePart;
+    }
+    
+    /**
+     * @return the signer certificate
+     */
+    public X509Certificate getSigner() {
+        return signer;
+    }
+    
+    /**
+     * @return the certificate chain of the signer
+     */
+    public List<X509Certificate> getCertChain() {
+        return certChain;
+    }
+    
+    /**
+     * Helper method for examining the xml signature
+     *
+     * @return the xml signature document
+     * @throws IOException if the xml signature doesn't exist or can't be read
+     * @throws XmlException if the xml signature is malformed
+     */
+    public SignatureDocument getSignatureDocument() throws IOException, XmlException {
+        // TODO: check for XXE
+        return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
+    }
+    
+    /**
+     * @return true, when the xml signature is valid, false otherwise
+     * 
+     * @throws EncryptedDocumentException if the signature can't be extracted or if its malformed
+     */
+    public boolean validate() {
+        KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
+        try {
+            Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
+            XPath xpath = XPathFactory.newInstance().newXPath();
+            NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
+            final int length = nl.getLength();
+            for (int i=0; i<length; i++) {
+                ((Element)nl.item(i)).setIdAttribute("Id", true);
+            }
+            
+            DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
+            domValidateContext.setProperty(XMLSEC_VALIDATE_MANIFEST, Boolean.TRUE);
+            domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
+
+            XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();
+            XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+            
+            boolean valid = xmlSignature.validate(domValidateContext);
+
+            if (valid) {
+                signer = keySelector.getSigner();
+                certChain = keySelector.getCertChain();
+            }
+            
+            return valid;
+        } catch (IOException e) {
+            String s = "error in reading document";
+            LOG.log(POILogger.ERROR, s, e);
+            throw new EncryptedDocumentException(s, e);
+        } catch (SAXException e) {
+            String s = "error in parsing document";
+            LOG.log(POILogger.ERROR, s, e);
+            throw new EncryptedDocumentException(s, e);
+        } catch (XPathExpressionException e) {
+            String s = "error in searching document with xpath expression";
+            LOG.log(POILogger.ERROR, s, e);
+            throw new EncryptedDocumentException(s, e);
+        } catch (MarshalException e) {
+            String s = "error in unmarshalling the signature";
+            LOG.log(POILogger.ERROR, s, e);
+            throw new EncryptedDocumentException(s, e);
+        } catch (XMLSignatureException e) {
+            String s = "error in validating the signature";
+            LOG.log(POILogger.ERROR, s, e);
+            throw new EncryptedDocumentException(s, e);
+        }
+    }
+}
index eec02ad4d58fd96f1ec6d232289cba96b8d62c9a..a6ce4226aeb6f30628fb3d951aa907bf873d9690 100644 (file)
 
 package org.apache.poi.poifs.crypt.dsig.facets;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.security.AccessController;
 import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.PrivilegedAction;
-import java.security.Provider;
-import java.security.Security;
 import java.util.List;
 
 import javax.xml.XMLConstants;
@@ -45,14 +38,11 @@ import javax.xml.crypto.dsig.XMLSignatureException;
 import javax.xml.crypto.dsig.XMLSignatureFactory;
 import javax.xml.crypto.dsig.spec.TransformParameterSpec;
 
-import org.apache.jcp.xml.dsig.internal.dom.DOMDigestMethod;
-import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
 import org.apache.poi.openxml4j.opc.PackageNamespaces;
 import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
 import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
-import org.apache.poi.util.SuppressForbidden;
 import org.w3c.dom.Document;
 
 /**
@@ -71,6 +61,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
 
     protected SignatureConfig signatureConfig;
 
+    @Override
     public void setSignatureConfig(SignatureConfig signatureConfig) {
         this.signatureConfig = signatureConfig;
     }
@@ -153,38 +144,7 @@ public abstract class SignatureFacet implements SignatureConfigurable {
             reference = sigFac.newReference(uri, digestMethod, transforms, type, id, digestValue);
         }
         
-        brokenJvmWorkaround(reference);
 
         return reference;
     }
-    
-    // helper method ... will be removed soon
-    public static void brokenJvmWorkaround(final Reference reference) {
-        final DigestMethod digestMethod = reference.getDigestMethod();
-        final String digestMethodUri = digestMethod.getAlgorithm();
-        
-        final Provider bcProv = Security.getProvider("BC");
-        if (bcProv != null && !DigestMethod.SHA1.equals(digestMethodUri)) {
-            // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
-            // overwrite standard message digest, if a digest <> SHA1 is used
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                @Override
-                @SuppressForbidden("Workaround for a bug, needs access to private JDK members (may fail in Java 9): https://bugzilla.redhat.com/show_bug.cgi?id=1155012")
-                public Void run() {
-                    try {
-                        Method m = DOMDigestMethod.class.getDeclaredMethod("getMessageDigestAlgorithm");
-                        m.setAccessible(true);
-                        String mdAlgo = (String)m.invoke(digestMethod);
-                        MessageDigest md = MessageDigest.getInstance(mdAlgo, bcProv);
-                        Field f = DOMReference.class.getDeclaredField("md");
-                        f.setAccessible(true);
-                        f.set(reference, md);
-                    } catch (Exception e) {
-                        LOG.log(POILogger.WARN, "Can't overwrite message digest (workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012)", e);
-                    }
-                    return null; // Void
-                }
-            });
-        }
-    }
 }
\ No newline at end of file
index 869978fe4e734f77b79042f65f18f5821f38f3a6..3db9b4f83704106ed777e7d613d733d1e40f6b65 100644 (file)
@@ -56,15 +56,17 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+
+import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.POITestCase;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackageAccess;
 import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
-import org.apache.poi.poifs.crypt.dsig.DigestInfo;
 import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
 import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
-import org.apache.poi.poifs.crypt.dsig.SignatureInfo.SignaturePart;
+import org.apache.poi.poifs.crypt.dsig.SignaturePart;
 import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
@@ -120,8 +122,6 @@ public class TestSignatureInfo {
         
         cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC);
         assertNotNull(cal);
-//        cal.set(2014, 7, 6, 21, 42, 12);
-//        cal.clear(Calendar.MILLISECOND);
 
         // don't run this test when we are using older Xerces as it triggers an XML Parser backwards compatibility issue 
         // in the xmlsec jar file
@@ -129,6 +129,11 @@ public class TestSignatureInfo {
         //System.out.println("Having: " + additionalJar);
         Assume.assumeTrue("Not running TestSignatureInfo because we are testing with additionaljar set to " + additionalJar, 
                 additionalJar == null || additionalJar.trim().length() == 0);
+        
+        System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
+        
+        // Set line.separator for bug61182
+        // System.setProperty("line.separator", "\n");
     }
 
     @Ignore("This test is very sensitive, it breaks with every little change to the produced XML")
@@ -198,18 +203,18 @@ public class TestSignatureInfo {
         if (sep == null || "\n".equals(sep)) {
             // Unix
             signExp =
-                "HDdvgXblLMiE6gZSoRSQUof6+aedrhK9i51we1n+4Q/ioqrQCeh5UkfQ8lD63nV4ZDbM4/pIVFi6VpMpN/HMnA"+
-                "UHeVdVUCVTgpn3Iz21Ymcd9/aerNov2BjHLhS8X3oUE+XTu2TbJLNmms0I9G4lfg6HWP9t7ZCXBXy6vyCMArc=";
+                "QkqTFQZjXagjRAoOWKpAGa8AR0rKqkSfBtfSWqtjBmTgyjarn+t2POHkpySIpheHAbg+90GKSH88ACMtPHbG7q"+
+                "FL4gtgAD9Kjew6j16j0IRBwy145UlPrSLFMfF7YF7UlU1k1LBkIlRJ6Fv4MAJl6XspuzZOZIUmHZrWrdxycUQ=";
         } else if ("\r\n".equals(sep)){
             // Windows
             signExp =
-                "jVW6EPMywZ8jr4+I4alDosXzqrVuDG4wTdrr+la8QVbXfLm6HOh9AUFlo5yUZuWo/1gXrrkc34UTYNzuslyrOx"+
-                "KqadPOIRKUssJzdCh/hKeTxs/YtyWkpGHggrUjrF/vUUIeIXRHo+1DCAh6ptoicviH/I/Dtoa5NgkEHVuOHk8=";
+                "GmAlL7+bT1r3FsMHJOp3pKg8betblYieZTjhMIrPZPRBbSzjO7KsYRGNtr0aOE3qr8xzyYJN6/8QdF5X7pUEUc"+
+                "2m8ctrm7s5o2vZTkAqk9ENJGDjBPXX7TnuVOiVeL1cJdtjHC2QpjtRwkFR+B54G6b1OXLOFuQpP3vqR3+/XXE=";
         } else {
             // Mac
             signExp =
-                "GSaOQp2eVRkQl2GJgWxoxFdCadJJnmmKeoQtIwGrP3zzk+BnLeytGLN3bqmwCTjvtG7DyxENS+92e2xq/MiC9b"+
-                "CtNUfXfCdM0M8fzAny/Ewn9HckIsxjBztmsryt/OZQaKu52VU0ohQu7bG+cGPzcM+qTEss+GUbD0sVAoC34HM=";
+                "NZedY/LNTYU4nAUEUhIOg5+fKdgVtzRXKmdD3v+47E7Mb84oeiUGv9cCEE91DU3StF/JFIhjOJqavOzKnCsNcz"+
+                "NJ4j/inggUl1OJUsicqIGQnA7E8vzWnN1kf5lINgJLv+0PyrrX9sQZbItzxUpgqyOFYcD0trid+31nRt4wtaA=";
         }
         
         String signAct = si.getSignatureParts().iterator().next().
@@ -721,24 +726,21 @@ public class TestSignatureInfo {
         SignatureInfo si = new SignatureInfo();
         si.setSignatureConfig(signatureConfig);
 
-        Document document = DocumentHelper.createDocument();
+        final Document document = DocumentHelper.createDocument();
+        final DOMSignContext xmlSignContext = si.createXMLSignContext(document);
 
         // operate
-        DigestInfo digestInfo = si.preSign(document, null);
+        final DOMSignedInfo signedInfo = si.preSign(xmlSignContext);
 
         // verify
-        assertNotNull(digestInfo);
-        LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo);
-        LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
-        assertEquals("Office OpenXML Document", digestInfo.description);
-        assertNotNull(digestInfo.hashAlgo);
-        assertNotNull(digestInfo.digestValue);
+        assertNotNull(signedInfo);
+        assertEquals("Office OpenXML Document", signatureConfig.getSignatureDescription());
 
         // setup: key material, signature value
-        byte[] signatureValue = si.signDigest(digestInfo.digestValue);
+        final String signatureValue = si.signDigest(xmlSignContext, signedInfo);
         
         // operate: postSign
-        si.postSign(document, signatureValue);
+        si.postSign(xmlSignContext, signatureValue);
 
         // verify: signature
         si.getSignatureConfig().setOpcPackage(pkgCopy);
index 7bcb080c316562b5efab52d36a7b6924e7d2de4e..d895c9ad3408e66aa9d1ac7c4c57dce2fec97661 100644 (file)
 ==================================================================== */
 package org.apache.poi.poifs.crypt.dsig;
 
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+
+import javax.xml.bind.DatatypeConverter;
+
 import org.apache.poi.poifs.crypt.HashAlgorithm;
 import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.junit.Assert.assertEquals;
-
 public class TestSignatureConfig {
     
-    @Ignore("failing in automated builds, due to issues loading security classes")
     @Test
+    @Ignore("failing in automated builds, due to issues loading security classes")
     public void testDigestAlgo() throws Exception {
         SignatureConfig sc = new SignatureConfig();
         assertEquals(HashAlgorithm.sha256, sc.getDigestAlgo());
         sc.setDigestAlgo(HashAlgorithm.sha1);
         assertEquals(HashAlgorithm.sha1, sc.getDigestAlgo());
     }
+    
+    @Test
+    public void testHashOids() throws IOException {
+        final String[][] checks = {
+            { "sha1", "MCEwCQYFKw4DAhoFAAQU" },
+            { "sha224", "MC0wDQYJYIZIAWUDBAIEBQAEHA==" },
+            { "sha256", "MDEwDQYJYIZIAWUDBAIBBQAEIA==" },
+            { "sha384", "MEEwDQYJYIZIAWUDBAICBQAEMA==" },
+            { "sha512", "MFEwDQYJYIZIAWUDBAIDBQAEQA==" },
+            { "ripemd128", "MB0wCQYFKyQDAgIFAAQQ" },
+            { "ripemd160", "MCEwCQYFKyQDAgEFAAQU" },
+            { "ripemd256", "MC0wCQYFKyQDAgMFAAQg" },
+        };
+
+        for (final String[] check : checks) {
+            final HashAlgorithm ha = HashAlgorithm.valueOf(check[0]);
+            try (final DigestOutputStream dos = new DigestOutputStream(ha, null)) {
+                final String magic = DatatypeConverter.printBase64Binary(dos.getHashMagic());
+                assertEquals("hash digest magic mismatches", check[1], magic);
+            }
+        }
+    }
 }