aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java192
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java62
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java13
-rw-r--r--src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java309
-rw-r--r--test-data/xmldsign/PPT2016withComment.pptxbin0 -> 36193 bytes
5 files changed, 421 insertions, 155 deletions
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
index f3184ab09f..9fa93f7a06 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
@@ -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);
}
/**
@@ -533,6 +596,16 @@ public class SignatureConfig {
}
/**
+ * @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)
*/
public String getUserAgent() {
@@ -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
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
index cff1e1d9ac..81e5b21971 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignaturePart.java
@@ -18,24 +18,31 @@
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;
+ }
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
index ee61027f70..5d4ccbd869 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
@@ -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);
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
index 7bb4f6ac84..dd2bc1705f 100644
--- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
+++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
@@ -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
index 0000000000..92154f9f0d
--- /dev/null
+++ b/test-data/xmldsign/PPT2016withComment.pptx
Binary files differ