]> source.dussan.org Git - poi.git/commitdiff
#62452 - Extract configuration while verifying XML signatures
authorAndreas Beeker <kiwiwings@apache.org>
Wed, 13 Jun 2018 20:06:08 +0000 (20:06 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Wed, 13 Jun 2018 20:06:08 +0000 (20:06 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1833477 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
test-data/xmldsign/PPT2016withComment.pptx [new file with mode: 0644]

index f3184ab09fcc43e21c390e15e9ed5285a80ee456..9fa93f7a06af8fd07c9dd7971cf8f098e4de64e8 100644 (file)
@@ -23,10 +23,14 @@ import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XADES_132_NS
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.UUID;
 
@@ -49,6 +53,7 @@ import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService;
 import org.apache.poi.poifs.crypt.dsig.services.TSPTimeStampService;
 import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
 import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
+import org.apache.poi.util.LocaleUtil;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.xml.security.signature.XMLSignature;
@@ -60,10 +65,16 @@ import org.w3c.dom.events.EventListener;
  * Apart of the thread local members (e.g. opc-package) most values will probably be constant, so
  * it might be configured centrally (e.g. by spring) 
  */
+@SuppressWarnings({"unused","WeakerAccess"})
 public class SignatureConfig {
 
+    public static final String SIGNATURE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
     private static final POILogger LOG = POILogFactory.getLogger(SignatureConfig.class);
-    
+    private static final String DigestMethod_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#sha224";
+    private static final String DigestMethod_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384";
+
+
     public interface SignatureConfigurable {
         void setSignatureConfig(SignatureConfig signatureConfig);        
     }
@@ -150,14 +161,20 @@ public class SignatureConfig {
      * with certain namespaces, so this EventListener is used to interfere
      * with the marshalling process.
      */
-    EventListener signatureMarshalListener;
+    private EventListener signatureMarshalListener;
 
     /**
      * Map of namespace uris to prefix
      * If a mapping is specified, the corresponding elements will be prefixed
      */
-    Map<String,String> namespacePrefixes = new HashMap<>();
-    
+    private final Map<String,String> namespacePrefixes = new HashMap<>();
+
+    /**
+     * if true, the signature config is updated based on the validated document
+     */
+    private boolean updateConfigOnValidate = false;
+
+
     /**
      * Inits and checks the config object.
      * If not set previously, complex configuration properties also get 
@@ -308,7 +325,36 @@ public class SignatureConfig {
     public void setExecutionTime(Date executionTime) {
         this.executionTime = executionTime;
     }
-    
+
+    /**
+     * @return the formatted execution time ({@link #SIGNATURE_TIME_FORMAT})
+     *
+     * @since POI 4.0.0
+     */
+    public String formatExecutionTime() {
+        final DateFormat fmt = new SimpleDateFormat(SIGNATURE_TIME_FORMAT, Locale.ROOT);
+        fmt.setTimeZone(LocaleUtil.TIMEZONE_UTC);
+        return fmt.format(getExecutionTime());
+    }
+
+    /**
+     * Sets the executionTime which is in standard format ({@link #SIGNATURE_TIME_FORMAT})
+     * @param executionTime the execution time
+     *
+     * @since POI 4.0.0
+     */
+    public void setExecutionTime(String executionTime) {
+        if (executionTime != null && !"".equals(executionTime)){
+            final DateFormat fmt = new SimpleDateFormat(SIGNATURE_TIME_FORMAT, Locale.ROOT);
+            fmt.setTimeZone(LocaleUtil.TIMEZONE_UTC);
+            try {
+                this.executionTime = fmt.parse(executionTime);
+            } catch (ParseException e) {
+                LOG.log(POILogger.WARN, "Illegal execution time: "+executionTime);
+            }
+        }
+    }
+
     /**
      * @return the service to be used for XAdES-EPES properties. There's no default implementation
      */
@@ -364,7 +410,24 @@ public class SignatureConfig {
      * @param canonicalizationMethod the default canonicalization method
      */
     public void setCanonicalizationMethod(String canonicalizationMethod) {
-        this.canonicalizationMethod = canonicalizationMethod;
+        this.canonicalizationMethod = verifyCanonicalizationMethod(canonicalizationMethod, CanonicalizationMethod.INCLUSIVE);
+    }
+
+    private static String verifyCanonicalizationMethod(String canonicalizationMethod, String defaultMethod) {
+        if (canonicalizationMethod == null || canonicalizationMethod.isEmpty()) {
+            return defaultMethod;
+        }
+
+        switch (canonicalizationMethod) {
+            case CanonicalizationMethod.INCLUSIVE:
+            case CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS:
+            case CanonicalizationMethod.ENVELOPED:
+            case CanonicalizationMethod.EXCLUSIVE:
+            case CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS:
+                return canonicalizationMethod;
+        }
+
+        throw new EncryptedDocumentException("Unknown CanonicalizationMethod: "+canonicalizationMethod);
     }
 
     /**
@@ -532,6 +595,16 @@ public class SignatureConfig {
         this.xadesDigestAlgo = xadesDigestAlgo;
     }
 
+    /**
+     * @param xadesDigestAlgo hash algorithm used for XAdES.
+     * When <code>null</code>, defaults to {@link #getDigestAlgo()}
+     *
+     * @since POI 4.0.0
+     */
+    public void setXadesDigestAlgo(String xadesDigestAlgo) {
+        this.xadesDigestAlgo = getDigestMethodAlgo(xadesDigestAlgo);
+    }
+
     /**
      * @return the user agent used for http communication (e.g. to the TSP)
      */
@@ -700,16 +773,17 @@ public class SignatureConfig {
      * @param namespacePrefixes the map of namespace uri (key) to prefix (value)
      */
     public void setNamespacePrefixes(Map<String, String> namespacePrefixes) {
-        this.namespacePrefixes = namespacePrefixes;
+        this.namespacePrefixes.clear();
+        this.namespacePrefixes.putAll(namespacePrefixes);
     }
 
     /**
      * helper method for null/default value handling
-     * @param value
-     * @param defaultValue
+     * @param value the value to be tested
+     * @param defaultValue the default value
      * @return if value is not null, return value otherwise defaultValue
      */
-    protected static <T> T nvl(T value, T defaultValue)  {
+    private static <T> T nvl(T value, T defaultValue)  {
         return value == null ? defaultValue : value;
     }
 
@@ -729,34 +803,92 @@ public class SignatureConfig {
             +getDigestAlgo()+" not supported for signing.");
         }
     }
-    
+
     /**
      * @return the uri for the main digest
      */
     public String getDigestMethodUri() {
         return getDigestMethodUri(getDigestAlgo());
     }
-    
+
     /**
-     * Sets the digest algorithm - currently only sha* and ripemd160 is supported.
+     * Converts the digest algorithm - currently only sha* and ripemd160 is supported.
      * MS Office only supports sha1, sha256, sha384, sha512. 
      * 
-     * @param digestAlgo the digest algorithm  
+     * @param digestAlgo the digest algorithm
      * @return the uri for the given digest
      */
     public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
         switch (digestAlgo) {
         case sha1:   return DigestMethod.SHA1;
-        case sha224: return "http://www.w3.org/2001/04/xmldsig-more#sha224";
+        case sha224: return DigestMethod_SHA224;
         case sha256: return DigestMethod.SHA256;
-        case sha384: return "http://www.w3.org/2001/04/xmldsig-more#sha384";
+        case sha384: return DigestMethod_SHA384;
         case sha512: return DigestMethod.SHA512;
         case ripemd160: return DigestMethod.RIPEMD160;
         default: throw new EncryptedDocumentException("Hash algorithm "
             +digestAlgo+" not supported for signing.");
         }
     }
-    
+
+    /**
+     * Converts the digest algorithm ur - currently only sha* and ripemd160 is supported.
+     * MS Office only supports sha1, sha256, sha384, sha512.
+     *
+     * @param digestAlgo the digest algorithm uri
+     * @return the hash algorithm for the given digest
+     */
+    private static HashAlgorithm getDigestMethodAlgo(String digestMethodUri) {
+        if (digestMethodUri == null || digestMethodUri.isEmpty()) {
+            return null;
+        }
+        switch (digestMethodUri) {
+            case DigestMethod.SHA1:   return HashAlgorithm.sha1;
+            case DigestMethod_SHA224: return HashAlgorithm.sha224;
+            case DigestMethod.SHA256: return HashAlgorithm.sha256;
+            case DigestMethod_SHA384: return HashAlgorithm.sha384;
+            case DigestMethod.SHA512: return HashAlgorithm.sha512;
+            case DigestMethod.RIPEMD160: return HashAlgorithm.ripemd160;
+            default: throw new EncryptedDocumentException("Hash algorithm "
+                    +digestMethodUri+" not supported for signing.");
+        }
+    }
+
+    /**
+     * Set the digest algorithm based on the method uri.
+     * This is used when a signature was successful validated and the signature
+     * configuration is updated
+     *
+     * @param signatureMethodUri the method uri
+     *
+     * @since POI 4.0.0
+     */
+    public void setSignatureMethodFromUri(final String signatureMethodUri) {
+        switch (signatureMethodUri) {
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1:
+                setDigestAlgo(HashAlgorithm.sha1);
+                break;
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA224:
+                setDigestAlgo(HashAlgorithm.sha224);
+                break;
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256:
+                setDigestAlgo(HashAlgorithm.sha256);
+                break;
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384:
+                setDigestAlgo(HashAlgorithm.sha384);
+                break;
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512:
+                setDigestAlgo(HashAlgorithm.sha512);
+                break;
+            case XMLSignature.ALGO_ID_SIGNATURE_RSA_RIPEMD160:
+                setDigestAlgo(HashAlgorithm.ripemd160);
+                break;
+            default: throw new EncryptedDocumentException("Hash algorithm "
+                    +signatureMethodUri+" not supported.");
+        }
+    }
+
+
     /**
      * @param signatureFactory the xml signature factory, saved as thread-local
      */
@@ -852,6 +984,28 @@ public class SignatureConfig {
      * @see <a href="http://docs.oracle.com/javase/7/docs/api/javax/xml/crypto/dsig/CanonicalizationMethod.html">javax.xml.crypto.dsig.CanonicalizationMethod</a>
      */
     public void setXadesCanonicalizationMethod(String xadesCanonicalizationMethod) {
-        this.xadesCanonicalizationMethod = xadesCanonicalizationMethod;
+        this.xadesCanonicalizationMethod = verifyCanonicalizationMethod(xadesCanonicalizationMethod, CanonicalizationMethod.EXCLUSIVE);
+    }
+
+    /**
+     * @return true, if the signature config is to be updated based on the successful validated document
+     *
+     * @since POI 4.0.0
+     */
+    public boolean isUpdateConfigOnValidate() {
+        return updateConfigOnValidate;
+    }
+
+    /**
+     * The signature config can be updated if a document is succesful validated.
+     * This flag is used for activating this modifications.
+     * Defaults to {@code false}<p>
+     *
+     * @param updateConfigOnValidate if true, update config on validate
+     *
+     * @since POI 4.0.0
+     */
+    public void setUpdateConfigOnValidate(boolean updateConfigOnValidate) {
+        this.updateConfigOnValidate = updateConfigOnValidate;
     }
-}
+}
\ No newline at end of file
index cff1e1d9ace1bb070a0f24fe7c9ad133fb6893f7..81e5b219717c5069a4981f51411a8021fa1ff7cd 100644 (file)
 package org.apache.poi.poifs.crypt.dsig;
 
 import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.MS_DIGSIG_NS;
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
 
 import java.io.IOException;
 import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
 
 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.namespace.NamespaceContext;
 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.ooxml.util.DocumentHelper;
+import org.apache.poi.openxml4j.opc.PackagePart;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.xmlbeans.XmlException;
@@ -92,7 +99,7 @@ public class SignaturePart {
         // TODO: check for XXE
         return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
     }
-    
+
     /**
      * @return true, when the xml signature is valid, false otherwise
      * 
@@ -100,9 +107,11 @@ public class SignaturePart {
      */
     public boolean validate() {
         KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
+        XPath xpath = XPathFactory.newInstance().newXPath();
+        xpath.setNamespaceContext(new XPathNSContext());
+
         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++) {
@@ -121,6 +130,7 @@ public class SignaturePart {
             if (valid) {
                 signer = keySelector.getSigner();
                 certChain = keySelector.getCertChain();
+                extractConfig(doc, xmlSignature);
             }
             
             return valid;
@@ -146,4 +156,50 @@ public class SignaturePart {
             throw new EncryptedDocumentException(s, e);
         }
     }
+
+    private void extractConfig(final Document doc, final XMLSignature xmlSignature) throws XPathExpressionException {
+        if (!signatureConfig.isUpdateConfigOnValidate()) {
+            return;
+        }
+
+        signatureConfig.setSigningCertificateChain(certChain);
+        signatureConfig.setSignatureMethodFromUri(xmlSignature.getSignedInfo().getSignatureMethod().getAlgorithm());
+
+        final XPath xpath = XPathFactory.newInstance().newXPath();
+        xpath.setNamespaceContext(new XPathNSContext());
+
+        final Map<String,Consumer<String>> m = new HashMap();
+        m.put("//mdssi:SignatureTime/mdssi:Value", signatureConfig::setExecutionTime);
+        m.put("//xd:ClaimedRole", signatureConfig::setXadesRole);
+        m.put("//dsss:SignatureComments", signatureConfig::setSignatureDescription);
+        m.put("//xd:QualifyingProperties//xd:SignedSignatureProperties//ds:DigestMethod/@Algorithm", signatureConfig::setXadesDigestAlgo);
+        m.put("//ds:CanonicalizationMethod", signatureConfig::setCanonicalizationMethod);
+
+        for (Map.Entry<String,Consumer<String>> me : m.entrySet()) {
+            String val = (String)xpath.compile(me.getKey()).evaluate(doc, XPathConstants.STRING);
+            me.getValue().accept(val);
+        }
+    }
+
+    private class XPathNSContext implements NamespaceContext {
+        final Map<String,String> nsMap = new HashMap<>();
+
+        {
+            for (Map.Entry<String,String> me : signatureConfig.getNamespacePrefixes().entrySet()) {
+                nsMap.put(me.getValue(), me.getKey());
+            }
+            nsMap.put("dsss", MS_DIGSIG_NS);
+            nsMap.put("ds", XML_DIGSIG_NS);
+        }
+
+        public String getNamespaceURI(String prefix) {
+            return nsMap.get(prefix);
+        }
+        public Iterator getPrefixes(String val) {
+            return null;
+        }
+        public String getPrefix(String uri) {
+            return null;
+        }
+    }
 }
index ee61027f70b2085209e0acd9e277961bb82821f0..5d4ccbd869af6218a743103553c58a785f112c27 100644 (file)
@@ -221,15 +221,11 @@ public class OOXMLSignatureFacet extends SignatureFacet {
         /*
          * SignatureTime
          */
-        DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
-        fmt.setTimeZone(LocaleUtil.TIMEZONE_UTC);
-        String nowStr = fmt.format(signatureConfig.getExecutionTime());
-        LOG.log(POILogger.DEBUG, "now: " + nowStr);
-
         SignatureTimeDocument sigTime = SignatureTimeDocument.Factory.newInstance();
         CTSignatureTime ctTime = sigTime.addNewSignatureTime();
         ctTime.setFormat("YYYY-MM-DDThh:mm:ssTZD");
-        ctTime.setValue(nowStr);
+        ctTime.setValue(signatureConfig.formatExecutionTime());
+        LOG.log(POILogger.DEBUG, "execution time: " + ctTime.getValue());
 
         Element n = (Element)document.importNode(ctTime.getDomNode(),true);
         List<XMLStructure> signatureTimeContent = new ArrayList<>();
@@ -253,6 +249,11 @@ public class OOXMLSignatureFacet extends SignatureFacet {
         SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
         CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
         ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
+
+        if (signatureConfig.getSignatureDescription() != null) {
+            ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
+        }
+
         Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
         n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);
 
index 7bb4f6ac843caa96f12fa08acbd0ae924516b896..dd2bc1705fccc88bc56cdf0a14eaa43cbd9c6e9c 100644 (file)
@@ -56,11 +56,14 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 
+import javax.xml.crypto.dsig.CanonicalizationMethod;
 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.ooxml.util.DocumentHelper;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
 import org.apache.poi.openxml4j.opc.PackageAccess;
 import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
@@ -69,6 +72,7 @@ import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
 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.OOXMLSignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet;
 import org.apache.poi.poifs.crypt.dsig.services.RevocationData;
@@ -77,7 +81,6 @@ import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
 import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
 import org.apache.poi.poifs.storage.RawDataUtil;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
-import org.apache.poi.ooxml.util.DocumentHelper;
 import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LocaleUtil;
 import org.apache.poi.util.POILogFactory;
@@ -186,7 +189,7 @@ public class TestSignatureInfo {
         bos.reset();
         pkg1.save(bos);
         pkg1.close();
-        
+
         XSSFWorkbook wb2 = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()));
         assertEquals("Test", wb2.getSheetAt(0).getRow(1).getCell(1).getStringCellValue());
         OPCPackage pkg2 = wb2.getPackage();
@@ -395,142 +398,173 @@ public class TestSignatureInfo {
     @Test
     public void testSignEnvelopingDocument() throws Exception {
         String testFile = "hello-world-unsigned.xlsx";
-        OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+        File sigCopy = testdata.getFile(testFile);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(50000);
+
+        final String execTimestr;
+
+
+        try (OPCPackage pkg = OPCPackage.open(copy(sigCopy), PackageAccess.READ_WRITE)) {
+
+            initKeyPair("Test", "CN=Test");
+            final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate());
+
+            // setup
+            SignatureConfig signatureConfig = new SignatureConfig();
+            signatureConfig.setOpcPackage(pkg);
+            signatureConfig.setKey(keyPair.getPrivate());
+
+            /*
+             * We need at least 2 certificates for the XAdES-C complete certificate
+             * refs construction.
+             */
+            List<X509Certificate> certificateChain = new ArrayList<>();
+            certificateChain.add(x509);
+            certificateChain.add(x509);
+            signatureConfig.setSigningCertificateChain(certificateChain);
+
+            signatureConfig.addSignatureFacet(new OOXMLSignatureFacet());
+            signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet());
+            signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet());
+            signatureConfig.addSignatureFacet(new XAdESSignatureFacet());
+            signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet());
+
+            // check for internet, no error means it works
+            boolean mockTsp = (getAccessError("http://timestamp.comodoca.com/rfc3161", true, 10000) != null);
+
+            // http://timestamping.edelweb.fr/service/tsp
+            // http://tsa.belgium.be/connect
+            // http://timestamp.comodoca.com/authenticode
+            // http://timestamp.comodoca.com/rfc3161
+            // http://services.globaltrustfinder.com/adss/tsa
+            signatureConfig.setTspUrl("http://timestamp.comodoca.com/rfc3161");
+            signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ...
+            signatureConfig.setTspOldProtocol(false);
+
+            signatureConfig.setXadesDigestAlgo(HashAlgorithm.sha512);
+            signatureConfig.setXadesRole("Xades Reviewer");
+            signatureConfig.setSignatureDescription("test xades signature");
+
+            execTimestr = signatureConfig.formatExecutionTime();
+
+            //set proxy info if any
+            String proxy = System.getProperty("http_proxy");
+            if (proxy != null && proxy.trim().length() > 0) {
+                signatureConfig.setProxyUrl(proxy);
+            }
 
-        initKeyPair("Test", "CN=Test");
-        final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate());
-        
-        // setup
-        SignatureConfig signatureConfig = new SignatureConfig();
-        signatureConfig.setOpcPackage(pkg);
-        signatureConfig.setKey(keyPair.getPrivate());
+            if (mockTsp) {
+                TimeStampService tspService = new TimeStampService() {
+                    @Override
+                    public byte[] timeStamp(byte[] data, RevocationData revocationData) {
+                        revocationData.addCRL(crl);
+                        return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252);
+                    }
 
-        /*
-         * We need at least 2 certificates for the XAdES-C complete certificate
-         * refs construction.
-         */
-        List<X509Certificate> certificateChain = new ArrayList<>();
-        certificateChain.add(x509);
-        certificateChain.add(x509);
-        signatureConfig.setSigningCertificateChain(certificateChain);
-        
-        signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet());
-        signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet());
-        signatureConfig.addSignatureFacet(new XAdESSignatureFacet());
-        signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet());
-        
-        // check for internet, no error means it works
-        boolean mockTsp = (getAccessError("http://timestamp.comodoca.com/rfc3161", true, 10000) != null);
-        
-        // http://timestamping.edelweb.fr/service/tsp
-        // http://tsa.belgium.be/connect
-        // http://timestamp.comodoca.com/authenticode
-        // http://timestamp.comodoca.com/rfc3161
-        // http://services.globaltrustfinder.com/adss/tsa
-        signatureConfig.setTspUrl("http://timestamp.comodoca.com/rfc3161");
-        signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ...
-        signatureConfig.setTspOldProtocol(false);
-        
-        //set proxy info if any
-        String proxy = System.getProperty("http_proxy");
-        if (proxy != null && proxy.trim().length() > 0) {
-            signatureConfig.setProxyUrl(proxy);
-        }
+                    @Override
+                    public void setSignatureConfig(SignatureConfig config) {
+                        // empty on purpose
+                    }
+                };
+                signatureConfig.setTspService(tspService);
+            } else {
+                TimeStampServiceValidator tspValidator = (validateChain, revocationData) -> {
+                    for (X509Certificate certificate : validateChain) {
+                        LOG.log(POILogger.DEBUG, "certificate: " + certificate.getSubjectX500Principal());
+                        LOG.log(POILogger.DEBUG, "validity: " + certificate.getNotBefore() + " - " + certificate.getNotAfter());
+                    }
+                };
+                signatureConfig.setTspValidator(tspValidator);
+                signatureConfig.setTspOldProtocol(signatureConfig.getTspUrl().contains("edelweb"));
+            }
 
-        if (mockTsp) {
-            TimeStampService tspService = new TimeStampService(){
-                @Override
-                public byte[] timeStamp(byte[] data, RevocationData revocationData) {
-                    revocationData.addCRL(crl);
-                    return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252);                
-                }
-                @Override
-                public void setSignatureConfig(SignatureConfig config) {
-                    // empty on purpose
-                }
-            };
-            signatureConfig.setTspService(tspService);
-        } else {
-            TimeStampServiceValidator tspValidator = (validateChain, revocationData) -> {
-                for (X509Certificate certificate : validateChain) {
-                    LOG.log(POILogger.DEBUG, "certificate: " + certificate.getSubjectX500Principal());
-                    LOG.log(POILogger.DEBUG, "validity: " + certificate.getNotBefore() + " - " + certificate.getNotAfter());
-                }
-            };
-            signatureConfig.setTspValidator(tspValidator);
-            signatureConfig.setTspOldProtocol(signatureConfig.getTspUrl().contains("edelweb"));
-        }
-        
-        final RevocationData revocationData = new RevocationData();
-        revocationData.addCRL(crl);
-        OCSPResp ocspResp = PkiTestUtils.createOcspResp(x509, false,
-                x509, x509, keyPair.getPrivate(), "SHA1withRSA", cal.getTimeInMillis());
-        revocationData.addOCSP(ocspResp.getEncoded());
+            final RevocationData revocationData = new RevocationData();
+            revocationData.addCRL(crl);
+            OCSPResp ocspResp = PkiTestUtils.createOcspResp(x509, false,
+                    x509, x509, keyPair.getPrivate(), "SHA1withRSA", cal.getTimeInMillis());
+            revocationData.addOCSP(ocspResp.getEncoded());
 
-        RevocationDataService revocationDataService = revocationChain -> revocationData;
-        signatureConfig.setRevocationDataService(revocationDataService);
+            RevocationDataService revocationDataService = revocationChain -> revocationData;
+            signatureConfig.setRevocationDataService(revocationDataService);
 
-        // operate
-        SignatureInfo si = new SignatureInfo();
-        si.setSignatureConfig(signatureConfig);
-        try {
-            si.confirmSignature();
-        } catch (RuntimeException e) {
-            pkg.close();
-            // only allow a ConnectException because of timeout, we see this in Jenkins from time to time...
-            if(e.getCause() == null) {
+            // operate
+            SignatureInfo si = new SignatureInfo();
+            si.setSignatureConfig(signatureConfig);
+            try {
+                si.confirmSignature();
+            } catch (RuntimeException e) {
+                pkg.close();
+                // only allow a ConnectException because of timeout, we see this in Jenkins from time to time...
+                if (e.getCause() == null) {
+                    throw e;
+                }
+                if ((e.getCause() instanceof ConnectException) || (e.getCause() instanceof SocketTimeoutException)) {
+                    Assume.assumeFalse("Only allowing ConnectException with 'timed out' as message here, but had: " + e,
+                            e.getCause().getMessage().contains("timed out"));
+                } else if (e.getCause() instanceof IOException) {
+                    Assume.assumeFalse("Only allowing IOException with 'Error contacting TSP server' as message here, but had: " + e,
+                            e.getCause().getMessage().contains("Error contacting TSP server"));
+                } else if (e.getCause() instanceof RuntimeException) {
+                    Assume.assumeFalse("Only allowing RuntimeException with 'This site is cur' as message here, but had: " + e,
+                            e.getCause().getMessage().contains("This site is cur"));
+                }
                 throw e;
             }
-            if((e.getCause() instanceof ConnectException) || (e.getCause() instanceof SocketTimeoutException)) {
-                Assume.assumeFalse("Only allowing ConnectException with 'timed out' as message here, but had: " + e,
-                        e.getCause().getMessage().contains("timed out"));
-            } else if (e.getCause() instanceof IOException) {
-                Assume.assumeFalse("Only allowing IOException with 'Error contacting TSP server' as message here, but had: " + e,
-                        e.getCause().getMessage().contains("Error contacting TSP server"));
-            } else if (e.getCause() instanceof RuntimeException) {
-                Assume.assumeFalse("Only allowing RuntimeException with 'This site is cur' as message here, but had: " + e,
-                        e.getCause().getMessage().contains("This site is cur"));
+
+            // verify
+            Iterator<SignaturePart> spIter = si.getSignatureParts().iterator();
+            assertTrue("Had: " + si.getSignatureConfig().getOpcPackage().
+                            getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN),
+                    spIter.hasNext());
+            SignaturePart sp = spIter.next();
+            boolean valid = sp.validate();
+            assertTrue(valid);
+
+            SignatureDocument sigDoc = sp.getSignatureDocument();
+            String declareNS =
+                    "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; "
+                            + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; ";
+
+            String digestValXQuery = declareNS +
+                    "$this/ds:Signature/ds:SignedInfo/ds:Reference";
+            for (ReferenceType rt : (ReferenceType[]) sigDoc.selectPath(digestValXQuery)) {
+                assertNotNull(rt.getDigestValue());
+                assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm());
             }
-            throw e;
-        }
-        
-        // verify
-        Iterator<SignaturePart> spIter = si.getSignatureParts().iterator();
-        assertTrue("Had: " + si.getSignatureConfig().getOpcPackage().
-                        getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN),
-                spIter.hasNext());
-        SignaturePart sp = spIter.next();
-        boolean valid = sp.validate();
-        assertTrue(valid);
-        
-        SignatureDocument sigDoc = sp.getSignatureDocument();
-        String declareNS = 
-            "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; "
-          + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; ";
-        
-        String digestValXQuery = declareNS +
-            "$this/ds:Signature/ds:SignedInfo/ds:Reference";
-        for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) {
-            assertNotNull(rt.getDigestValue());
-            assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm());
+
+            String certDigestXQuery = declareNS +
+                    "$this//xades:SigningCertificate/xades:Cert/xades:CertDigest";
+            XmlObject xoList[] = sigDoc.selectPath(certDigestXQuery);
+            assertEquals(xoList.length, 1);
+            DigestAlgAndValueType certDigest = (DigestAlgAndValueType) xoList[0];
+            assertNotNull(certDigest.getDigestValue());
+
+            String qualPropXQuery = declareNS +
+                    "$this/ds:Signature/ds:Object/xades:QualifyingProperties";
+            xoList = sigDoc.selectPath(qualPropXQuery);
+            assertEquals(xoList.length, 1);
+            QualifyingPropertiesType qualProp = (QualifyingPropertiesType) xoList[0];
+            boolean qualPropXsdOk = qualProp.validate();
+            assertTrue(qualPropXsdOk);
+
+            pkg.save(bos);
         }
 
-        String certDigestXQuery = declareNS +
-            "$this//xades:SigningCertificate/xades:Cert/xades:CertDigest";
-        XmlObject xoList[] = sigDoc.selectPath(certDigestXQuery);
-        assertEquals(xoList.length, 1);
-        DigestAlgAndValueType certDigest = (DigestAlgAndValueType)xoList[0];
-        assertNotNull(certDigest.getDigestValue());
-
-        String qualPropXQuery = declareNS +
-            "$this/ds:Signature/ds:Object/xades:QualifyingProperties";
-        xoList = sigDoc.selectPath(qualPropXQuery);
-        assertEquals(xoList.length, 1);
-        QualifyingPropertiesType qualProp = (QualifyingPropertiesType)xoList[0];
-        boolean qualPropXsdOk = qualProp.validate();
-        assertTrue(qualPropXsdOk);
+        try (OPCPackage pkg = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray()))) {
+            SignatureConfig signatureConfig = new SignatureConfig();
+            signatureConfig.setOpcPackage(pkg);
+            signatureConfig.setUpdateConfigOnValidate(true);
 
-        pkg.close();
+            SignatureInfo si = new SignatureInfo();
+            si.setSignatureConfig(signatureConfig);
+
+            assertTrue(si.verifySignature());
+
+            assertEquals(HashAlgorithm.sha512, signatureConfig.getXadesDigestAlgo());
+            assertEquals("Xades Reviewer", signatureConfig.getXadesRole());
+            assertEquals("test xades signature", signatureConfig.getSignatureDescription());
+            assertEquals(execTimestr, signatureConfig.formatExecutionTime());
+        }
     }
 
     public static String getAccessError(String destinationUrl, boolean fireRequest, int timeout) {
@@ -698,6 +732,27 @@ public class TestSignatureInfo {
         }
     }
 
+    @Test
+    public void testRetrieveCertificate() throws InvalidFormatException, IOException {
+        SignatureConfig sic = new SignatureConfig();
+        final File file = testdata.getFile("PPT2016withComment.pptx");
+        try (final OPCPackage pkg = OPCPackage.open(file, PackageAccess.READ)) {
+            sic.setOpcPackage(pkg);
+            sic.setUpdateConfigOnValidate(true);
+            SignatureInfo si = new SignatureInfo();
+            si.setSignatureConfig(sic);
+            assertTrue(si.verifySignature());
+        }
+
+        final List<X509Certificate> certs = sic.getSigningCertificateChain();
+        assertEquals(1, certs.size());
+        assertEquals("CN=Test", certs.get(0).getSubjectDN().getName());
+        assertEquals("SuperDuper-Reviewer", sic.getXadesRole());
+        assertEquals("Purpose for signing", sic.getSignatureDescription());
+        assertEquals("2018-06-10T09:00:54Z", sic.formatExecutionTime());
+        assertEquals(CanonicalizationMethod.INCLUSIVE, sic.getCanonicalizationMethod());
+    }
+
     private SignatureConfig prepareConfig(String alias, String signerDn, String pfxInput) throws Exception {
         initKeyPair(alias, signerDn, pfxInput);
 
diff --git a/test-data/xmldsign/PPT2016withComment.pptx b/test-data/xmldsign/PPT2016withComment.pptx
new file mode 100644 (file)
index 0000000..92154f9
Binary files /dev/null and b/test-data/xmldsign/PPT2016withComment.pptx differ