#62452 - Extract configuration while verifying XML signatures

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1833477 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-06-13 20:06:08 +00:00
parent d101d9100c
commit 5169036f66
5 changed files with 420 additions and 154 deletions

View 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.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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.TSPTimeStampService;
import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator; 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.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignature;
@ -60,9 +65,15 @@ import org.w3c.dom.events.EventListener;
* Apart of the thread local members (e.g. opc-package) most values will probably be constant, so * 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) * it might be configured centrally (e.g. by spring)
*/ */
@SuppressWarnings({"unused","WeakerAccess"})
public class SignatureConfig { 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 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 { public interface SignatureConfigurable {
void setSignatureConfig(SignatureConfig signatureConfig); void setSignatureConfig(SignatureConfig signatureConfig);
@ -150,13 +161,19 @@ public class SignatureConfig {
* with certain namespaces, so this EventListener is used to interfere * with certain namespaces, so this EventListener is used to interfere
* with the marshalling process. * with the marshalling process.
*/ */
EventListener signatureMarshalListener; private EventListener signatureMarshalListener;
/** /**
* Map of namespace uris to prefix * Map of namespace uris to prefix
* If a mapping is specified, the corresponding elements will be prefixed * 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. * Inits and checks the config object.
@ -309,6 +326,35 @@ public class SignatureConfig {
this.executionTime = 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 * @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 * @param canonicalizationMethod the default canonicalization method
*/ */
public void setCanonicalizationMethod(String canonicalizationMethod) { 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; 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) * @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) * @param namespacePrefixes the map of namespace uri (key) to prefix (value)
*/ */
public void setNamespacePrefixes(Map<String, String> namespacePrefixes) { public void setNamespacePrefixes(Map<String, String> namespacePrefixes) {
this.namespacePrefixes = namespacePrefixes; this.namespacePrefixes.clear();
this.namespacePrefixes.putAll(namespacePrefixes);
} }
/** /**
* helper method for null/default value handling * helper method for null/default value handling
* @param value * @param value the value to be tested
* @param defaultValue * @param defaultValue the default value
* @return if value is not null, return value otherwise defaultValue * @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; return value == null ? defaultValue : value;
} }
@ -738,7 +812,7 @@ public class SignatureConfig {
} }
/** /**
* 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. * MS Office only supports sha1, sha256, sha384, sha512.
* *
* @param digestAlgo the digest algorithm * @param digestAlgo the digest algorithm
@ -747,9 +821,9 @@ public class SignatureConfig {
public static String getDigestMethodUri(HashAlgorithm digestAlgo) { public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
switch (digestAlgo) { switch (digestAlgo) {
case sha1: return DigestMethod.SHA1; 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 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 sha512: return DigestMethod.SHA512;
case ripemd160: return DigestMethod.RIPEMD160; case ripemd160: return DigestMethod.RIPEMD160;
default: throw new EncryptedDocumentException("Hash algorithm " default: throw new EncryptedDocumentException("Hash algorithm "
@ -757,6 +831,64 @@ public class SignatureConfig {
} }
} }
/**
* 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 * @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> * @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) { 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;
} }
} }

View File

@ -18,24 +18,31 @@
package org.apache.poi.poifs.crypt.dsig; package org.apache.poi.poifs.crypt.dsig;
import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; 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.io.IOException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.xml.crypto.MarshalException; import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ooxml.util.DocumentHelper; 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.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
@ -100,9 +107,11 @@ public class SignaturePart {
*/ */
public boolean validate() { public boolean validate() {
KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new XPathNSContext());
try { try {
Document doc = DocumentHelper.readDocument(signaturePart.getInputStream()); Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET); NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
final int length = nl.getLength(); final int length = nl.getLength();
for (int i=0; i<length; i++) { for (int i=0; i<length; i++) {
@ -121,6 +130,7 @@ public class SignaturePart {
if (valid) { if (valid) {
signer = keySelector.getSigner(); signer = keySelector.getSigner();
certChain = keySelector.getCertChain(); certChain = keySelector.getCertChain();
extractConfig(doc, xmlSignature);
} }
return valid; return valid;
@ -146,4 +156,50 @@ public class SignaturePart {
throw new EncryptedDocumentException(s, e); 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;
}
}
} }

View File

@ -221,15 +221,11 @@ public class OOXMLSignatureFacet extends SignatureFacet {
/* /*
* SignatureTime * 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(); SignatureTimeDocument sigTime = SignatureTimeDocument.Factory.newInstance();
CTSignatureTime ctTime = sigTime.addNewSignatureTime(); CTSignatureTime ctTime = sigTime.addNewSignatureTime();
ctTime.setFormat("YYYY-MM-DDThh:mm:ssTZD"); 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); Element n = (Element)document.importNode(ctTime.getDomNode(),true);
List<XMLStructure> signatureTimeContent = new ArrayList<>(); List<XMLStructure> signatureTimeContent = new ArrayList<>();
@ -253,6 +249,11 @@ public class OOXMLSignatureFacet extends SignatureFacet {
SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1(); CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
if (signatureConfig.getSignatureDescription() != null) {
ctSigV1.setSignatureComments(signatureConfig.getSignatureDescription());
}
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);

View File

@ -56,11 +56,14 @@ import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.dom.DOMSignContext;
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo; import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.POITestCase; 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.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; 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.SignaturePart;
import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet; 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.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.XAdESSignatureFacet;
import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet;
import org.apache.poi.poifs.crypt.dsig.services.RevocationData; 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.crypt.dsig.services.TimeStampServiceValidator;
import org.apache.poi.poifs.storage.RawDataUtil; import org.apache.poi.poifs.storage.RawDataUtil;
import org.apache.poi.ss.usermodel.WorkbookFactory; 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.IOUtils;
import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
@ -395,142 +398,173 @@ public class TestSignatureInfo {
@Test @Test
public void testSignEnvelopingDocument() throws Exception { public void testSignEnvelopingDocument() throws Exception {
String testFile = "hello-world-unsigned.xlsx"; 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);
initKeyPair("Test", "CN=Test"); final String execTimestr;
final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate());
// setup
SignatureConfig signatureConfig = new SignatureConfig();
signatureConfig.setOpcPackage(pkg);
signatureConfig.setKey(keyPair.getPrivate());
/* try (OPCPackage pkg = OPCPackage.open(copy(sigCopy), PackageAccess.READ_WRITE)) {
* 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()); initKeyPair("Test", "CN=Test");
signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet()); final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate());
signatureConfig.addSignatureFacet(new XAdESSignatureFacet());
signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet());
// check for internet, no error means it works // setup
boolean mockTsp = (getAccessError("http://timestamp.comodoca.com/rfc3161", true, 10000) != null); SignatureConfig signatureConfig = new SignatureConfig();
signatureConfig.setOpcPackage(pkg);
signatureConfig.setKey(keyPair.getPrivate());
// http://timestamping.edelweb.fr/service/tsp /*
// http://tsa.belgium.be/connect * We need at least 2 certificates for the XAdES-C complete certificate
// http://timestamp.comodoca.com/authenticode * refs construction.
// http://timestamp.comodoca.com/rfc3161 */
// http://services.globaltrustfinder.com/adss/tsa List<X509Certificate> certificateChain = new ArrayList<>();
signatureConfig.setTspUrl("http://timestamp.comodoca.com/rfc3161"); certificateChain.add(x509);
signatureConfig.setTspRequestPolicy(null); // comodoca request fails, if default policy is set ... certificateChain.add(x509);
signatureConfig.setTspOldProtocol(false); signatureConfig.setSigningCertificateChain(certificateChain);
//set proxy info if any signatureConfig.addSignatureFacet(new OOXMLSignatureFacet());
String proxy = System.getProperty("http_proxy"); signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet());
if (proxy != null && proxy.trim().length() > 0) { signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet());
signatureConfig.setProxyUrl(proxy); signatureConfig.addSignatureFacet(new XAdESSignatureFacet());
} signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet());
if (mockTsp) { // check for internet, no error means it works
TimeStampService tspService = new TimeStampService(){ boolean mockTsp = (getAccessError("http://timestamp.comodoca.com/rfc3161", true, 10000) != null);
@Override
public byte[] timeStamp(byte[] data, RevocationData revocationData) { // http://timestamping.edelweb.fr/service/tsp
revocationData.addCRL(crl); // http://tsa.belgium.be/connect
return "time-stamp-token".getBytes(LocaleUtil.CHARSET_1252); // 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);
}
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());
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) {
throw e;
} }
@Override if ((e.getCause() instanceof ConnectException) || (e.getCause() instanceof SocketTimeoutException)) {
public void setSignatureConfig(SignatureConfig config) { Assume.assumeFalse("Only allowing ConnectException with 'timed out' as message here, but had: " + e,
// empty on purpose 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"));
} }
};
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());
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) {
throw e; 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, // verify
e.getCause().getMessage().contains("timed out")); Iterator<SignaturePart> spIter = si.getSignatureParts().iterator();
} else if (e.getCause() instanceof IOException) { assertTrue("Had: " + si.getSignatureConfig().getOpcPackage().
Assume.assumeFalse("Only allowing IOException with 'Error contacting TSP server' as message here, but had: " + e, getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN),
e.getCause().getMessage().contains("Error contacting TSP server")); spIter.hasNext());
} else if (e.getCause() instanceof RuntimeException) { SignaturePart sp = spIter.next();
Assume.assumeFalse("Only allowing RuntimeException with 'This site is cur' as message here, but had: " + e, boolean valid = sp.validate();
e.getCause().getMessage().contains("This site is cur")); 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;
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);
} }
// verify try (OPCPackage pkg = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray()))) {
Iterator<SignaturePart> spIter = si.getSignatureParts().iterator(); SignatureConfig signatureConfig = new SignatureConfig();
assertTrue("Had: " + si.getSignatureConfig().getOpcPackage(). signatureConfig.setOpcPackage(pkg);
getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN), signatureConfig.setUpdateConfigOnValidate(true);
spIter.hasNext());
SignaturePart sp = spIter.next();
boolean valid = sp.validate();
assertTrue(valid);
SignatureDocument sigDoc = sp.getSignatureDocument(); SignatureInfo si = new SignatureInfo();
String declareNS = si.setSignatureConfig(signatureConfig);
"declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; "
+ "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; ";
String digestValXQuery = declareNS + assertTrue(si.verifySignature());
"$this/ds:Signature/ds:SignedInfo/ds:Reference";
for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) { assertEquals(HashAlgorithm.sha512, signatureConfig.getXadesDigestAlgo());
assertNotNull(rt.getDigestValue()); assertEquals("Xades Reviewer", signatureConfig.getXadesRole());
assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm()); assertEquals("test xades signature", signatureConfig.getSignatureDescription());
assertEquals(execTimestr, signatureConfig.formatExecutionTime());
} }
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.close();
} }
public static String getAccessError(String destinationUrl, boolean fireRequest, int timeout) { 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 { private SignatureConfig prepareConfig(String alias, String signerDn, String pfxInput) throws Exception {
initKeyPair(alias, signerDn, pfxInput); initKeyPair(alias, signerDn, pfxInput);

Binary file not shown.