();
+ NodeList relationshipNodes = relationshipsElement.getElementsByTagName("*");
+ int nodeCount = relationshipNodes.getLength();
+ for (int nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++) {
+ Node relationshipNode = relationshipNodes.item(0);
+ Element relationshipElement = (Element) relationshipNode;
+ LOG.debug("unsorted Id: " + relationshipElement.getAttribute("Id"));
+ relationshipElements.add(relationshipElement);
+ relationshipsElement.removeChild(relationshipNode);
+ }
+ Collections.sort(relationshipElements, new RelationshipComparator());
+ for (Element relationshipElement : relationshipElements) {
+ LOG.debug("sorted Id: " + relationshipElement.getAttribute("Id"));
+ relationshipsElement.appendChild(relationshipElement);
+ }
+ }
+
+ private String toString(Node dom) throws TransformerException {
+ Source source = new DOMSource(dom);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ /*
+ * We have to omit the ?xml declaration if we want to embed the
+ * document.
+ */
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ return stringWriter.getBuffer().toString();
+ }
+
+ private OctetStreamData toOctetStreamData(Node node) throws TransformerException {
+ Source source = new DOMSource(node);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ Result result = new StreamResult(outputStream);
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ LOG.debug("result: " + new String(outputStream.toByteArray()));
+ return new OctetStreamData(new ByteArrayInputStream(outputStream.toByteArray()));
+ }
+
+ private Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
+ InputSource inputSource = new InputSource(documentInputStream);
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.parse(inputSource);
+ return document;
+ }
+
+ public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
+ LOG.debug("transform(data,context,os)");
+ return null;
+ }
+
+ public boolean isFeatureSupported(String feature) {
+ LOG.debug("isFeatureSupported(feature)");
+ return false;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java
new file mode 100644
index 0000000000..eb7c767b97
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/ooxml/package-info.java
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+/**
+ * This package contains implementation classes for the Office Open XML Signature Service.
+ */
+package org.apache.poi.ooxml.signature.service.signer.ooxml;
+
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java
new file mode 100644
index 0000000000..60ff0dad9e
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/signer/package-info.java
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+/**
+ * This package contains implementation classes for the Signature Service SPI.
+ */
+package org.apache.poi.ooxml.signature.service.signer;
+
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java
new file mode 100644
index 0000000000..4ed07ffb18
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/AuthenticationService.java
@@ -0,0 +1,56 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.spi;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for authentication service components.
+ */
+public interface AuthenticationService {
+
+ /**
+ * Validates the given certificate chain. After the client has
+ * verified the authentication signature, it will invoke this method on your
+ * authentication service component. The implementation of this method
+ * should validate the given certificate chain. This validation could be
+ * based on PKI validation, or could be based on simply trusting the
+ * incoming public key. The actual implementation is very dependent on your
+ * type of application. This method should only be used for certificate
+ * validation.
+ *
+ *
+ * Check out jTrust for an
+ * implementation of a PKI validation framework.
+ *
+ *
+ * @param certificateChain
+ * the X509 authentication certificate chain of the citizen.
+ * @throws SecurityException
+ * in case the certificate chain is invalid/not accepted.
+ */
+ void validateCertificateChain(List certificateChain) throws SecurityException;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java
new file mode 100644
index 0000000000..1a2b6b7309
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/DigestInfo.java
@@ -0,0 +1,54 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.spi;
+
+import java.io.Serializable;
+
+/**
+ * Digest Information data transfer class.
+ */
+public class DigestInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Main constructor.
+ *
+ * @param digestValue
+ * @param digestAlgo
+ * @param description
+ */
+ public DigestInfo(byte[] digestValue, String digestAlgo, String description) {
+ this.digestValue = digestValue;
+ this.digestAlgo = digestAlgo;
+ this.description = description;
+ }
+
+ public final byte[] digestValue;
+
+ public final String description;
+
+ public final String digestAlgo;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java
new file mode 100644
index 0000000000..495bd57e65
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/InsecureClientEnvironmentException.java
@@ -0,0 +1,64 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.spi;
+
+/**
+ * Insecure Client Environment Exception.
+ */
+public class InsecureClientEnvironmentException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean warnOnly;
+
+ /**
+ * Default constructor.
+ */
+ public InsecureClientEnvironmentException() {
+ this(false);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param warnOnly
+ * only makes that the citizen is warned about a possible
+ * insecure enviroment.
+ */
+ public InsecureClientEnvironmentException(boolean warnOnly) {
+ this.warnOnly = warnOnly;
+ }
+
+ /**
+ * If set the eID Applet will only give a warning on case the server-side
+ * marks the client environment as being insecure. Else the eID Applet will
+ * abort the requested eID operation.
+ *
+ * @return
+ */
+ public boolean isWarnOnly() {
+ return this.warnOnly;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java
new file mode 100644
index 0000000000..f285c23c54
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SecureClientEnvironmentService.java
@@ -0,0 +1,73 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.spi;
+
+import java.util.List;
+
+/**
+ * Interface for security environment service components. Can be used by the eID
+ * Applet Service to check the client environment security requirements.
+ */
+public interface SecureClientEnvironmentService {
+
+ /**
+ * Checks whether the client environment is secure enough for this web
+ * application.
+ *
+ * @param javaVersion
+ * the version of the Java JRE on the client machine.
+ * @param javaVendor
+ * the vendor of the Java JRE on the client machine.
+ * @param osName
+ * the name of the operating system on the client machine.
+ * @param osArch
+ * the architecture of the client machine.
+ * @param osVersion
+ * the operating system version of the client machine.
+ * @param userAgent
+ * the user agent, i.e. browser, used on the client machine.
+ * @param navigatorAppName
+ * the optional navigator application name (browser)
+ * @param navigatorAppVersion
+ * the optional navigator application version (browser version)
+ * @param navigatorUserAgent
+ * the optional optional navigator user agent name.
+ * @param remoteAddress
+ * the address of the client machine.
+ * @param sslKeySize
+ * the key size of the SSL session used between server and
+ * client.
+ * @param sslCipherSuite
+ * the cipher suite of the SSL session used between server and
+ * client.
+ * @param readerList
+ * the list of smart card readers present on the client machine.
+ * @throws InsecureClientEnvironmentException
+ * if the client env is found not to be secure enough.
+ */
+ void checkSecureClientEnvironment(String javaVersion, String javaVendor, String osName, String osArch, String osVersion, String userAgent,
+ String navigatorAppName, String navigatorAppVersion, String navigatorUserAgent, String remoteAddress, int sslKeySize,
+ String sslCipherSuite, List readerList) throws InsecureClientEnvironmentException;
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java
new file mode 100644
index 0000000000..6b86b2fb14
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/SignatureService.java
@@ -0,0 +1,77 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.spi;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for signature service component.
+ */
+public interface SignatureService {
+
+ /**
+ * Gives back the digest algorithm to be used for construction of the digest
+ * infos of the preSign method. Return a digest algorithm here if you want
+ * to let the client sign some locally stored files. Return
+ * null
if no pre-sign digest infos are required.
+ *
+ * @return
+ * @see #preSign(List, List)
+ */
+ String getFilesDigestAlgorithm();
+
+ /**
+ * Pre-sign callback method. Depending on the configuration some parameters
+ * are passed. The returned value will be signed by the eID Applet.
+ *
+ *
+ * TODO: service must be able to throw some exception on failure.
+ *
+ *
+ * @param digestInfos
+ * the optional list of digest infos.
+ * @param signingCertificateChain
+ * the optional list of certificates.
+ * @return the digest to be signed.
+ * @throws NoSuchAlgorithmException
+ */
+ DigestInfo preSign(List digestInfos, List signingCertificateChain) throws NoSuchAlgorithmException;
+
+ /**
+ * Post-sign callback method. Received the signature value. Depending on the
+ * configuration the signing certificate chain is also obtained.
+ *
+ *
+ * TODO: service must be able to throw some exception on failure.
+ *
+ *
+ * @param signatureValue
+ * @param signingCertificateChain
+ * the optional chain of signing certificates.
+ */
+ void postSign(byte[] signatureValue, List signingCertificateChain);
+}
diff --git a/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java
new file mode 100644
index 0000000000..1b4a89f70d
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/ooxml/signature/service/spi/package-info.java
@@ -0,0 +1,28 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+/**
+ * This package contains the service provider interfaces.
+ */
+package org.apache.poi.ooxml.signature.service.spi;
+
diff --git a/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx b/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx
new file mode 100644
index 0000000000..5162b67c60
Binary files /dev/null and b/src/ooxml/testcases/hello-world-office-2010-technical-preview-unsigned.docx differ
diff --git a/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx b/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx
new file mode 100644
index 0000000000..cbd4277564
Binary files /dev/null and b/src/ooxml/testcases/hello-world-office-2010-technical-preview.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed-twice.docx b/src/ooxml/testcases/hello-world-signed-twice.docx
new file mode 100644
index 0000000000..96c91e957e
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed-twice.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed.docx b/src/ooxml/testcases/hello-world-signed.docx
new file mode 100644
index 0000000000..79a7bbb81f
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.docx differ
diff --git a/src/ooxml/testcases/hello-world-signed.pptx b/src/ooxml/testcases/hello-world-signed.pptx
new file mode 100644
index 0000000000..9b37033f54
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.pptx differ
diff --git a/src/ooxml/testcases/hello-world-signed.xlsx b/src/ooxml/testcases/hello-world-signed.xlsx
new file mode 100644
index 0000000000..0d45c53ede
Binary files /dev/null and b/src/ooxml/testcases/hello-world-signed.xlsx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.docx b/src/ooxml/testcases/hello-world-unsigned.docx
new file mode 100644
index 0000000000..1790c961ce
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.docx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.pptx b/src/ooxml/testcases/hello-world-unsigned.pptx
new file mode 100644
index 0000000000..ca42529a9a
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.pptx differ
diff --git a/src/ooxml/testcases/hello-world-unsigned.xlsx b/src/ooxml/testcases/hello-world-unsigned.xlsx
new file mode 100644
index 0000000000..b99143e92c
Binary files /dev/null and b/src/ooxml/testcases/hello-world-unsigned.xlsx differ
diff --git a/src/ooxml/testcases/invalidsig.docx b/src/ooxml/testcases/invalidsig.docx
new file mode 100644
index 0000000000..c448e819a0
Binary files /dev/null and b/src/ooxml/testcases/invalidsig.docx differ
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java
new file mode 100644
index 0000000000..a307ee0906
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/PkiTestUtils.java
@@ -0,0 +1,199 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.signer;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.joda.time.DateTime;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class PkiTestUtils {
+
+ public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[] { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14 };
+
+ private PkiTestUtils() {
+ super();
+ }
+
+ static KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ SecureRandom random = new SecureRandom();
+ keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), random);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ return keyPair;
+ }
+
+ private static SubjectKeyIdentifier createSubjectKeyId(PublicKey publicKey) throws IOException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject());
+ return new SubjectKeyIdentifier(info);
+ }
+
+ private static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey publicKey) throws IOException {
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject());
+
+ return new AuthorityKeyIdentifier(info);
+ }
+
+ static X509Certificate generateCertificate(PublicKey subjectPublicKey, String subjectDn, DateTime notBefore, DateTime notAfter,
+ X509Certificate issuerCertificate, PrivateKey issuerPrivateKey, boolean caFlag, int pathLength, String crlUri,
+ String ocspUri, KeyUsage keyUsage) throws IOException, InvalidKeyException, IllegalStateException,
+ NoSuchAlgorithmException, SignatureException, CertificateException {
+ String signatureAlgorithm = "SHA1withRSA";
+ X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
+ certificateGenerator.reset();
+ certificateGenerator.setPublicKey(subjectPublicKey);
+ certificateGenerator.setSignatureAlgorithm(signatureAlgorithm);
+ certificateGenerator.setNotBefore(notBefore.toDate());
+ certificateGenerator.setNotAfter(notAfter.toDate());
+ X509Principal issuerDN;
+ if (null != issuerCertificate) {
+ issuerDN = new X509Principal(issuerCertificate.getSubjectX500Principal().toString());
+ } else {
+ issuerDN = new X509Principal(subjectDn);
+ }
+ certificateGenerator.setIssuerDN(issuerDN);
+ certificateGenerator.setSubjectDN(new X509Principal(subjectDn));
+ certificateGenerator.setSerialNumber(new BigInteger(128, new SecureRandom()));
+
+ certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false, createSubjectKeyId(subjectPublicKey));
+ PublicKey issuerPublicKey;
+ issuerPublicKey = subjectPublicKey;
+ certificateGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, createAuthorityKeyId(issuerPublicKey));
+
+ if (caFlag) {
+ if (-1 == pathLength) {
+ certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(true));
+ } else {
+ certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(pathLength));
+ }
+ }
+
+ if (null != crlUri) {
+ GeneralName gn = new GeneralName(GeneralName.uniformResourceIdentifier, new DERIA5String(crlUri));
+ GeneralNames gns = new GeneralNames(new DERSequence(gn));
+ DistributionPointName dpn = new DistributionPointName(0, gns);
+ DistributionPoint distp = new DistributionPoint(dpn, null, null);
+ certificateGenerator.addExtension(X509Extensions.CRLDistributionPoints, false, new DERSequence(distp));
+ }
+
+ if (null != ocspUri) {
+ GeneralName ocspName = new GeneralName(GeneralName.uniformResourceIdentifier, ocspUri);
+ AuthorityInformationAccess authorityInformationAccess = new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod, ocspName);
+ certificateGenerator.addExtension(X509Extensions.AuthorityInfoAccess.getId(), false, authorityInformationAccess);
+ }
+
+ if (null != keyUsage) {
+ certificateGenerator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
+ }
+
+ X509Certificate certificate;
+ certificate = certificateGenerator.generate(issuerPrivateKey);
+
+ /*
+ * Next certificate factory trick is needed to make sure that the
+ * certificate delivered to the caller is provided by the default
+ * security provider instead of BouncyCastle. If we don't do this trick
+ * we might run into trouble when trying to use the CertPath validator.
+ */
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate.getEncoded()));
+ return certificate;
+ }
+
+ static Document loadDocument(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException {
+ InputSource inputSource = new InputSource(documentInputStream);
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.parse(inputSource);
+ return document;
+ }
+
+ static String toString(Node dom) throws TransformerException {
+ Source source = new DOMSource(dom);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ /*
+ * We have to omit the ?xml declaration if we want to embed the
+ * document.
+ */
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ return stringWriter.getBuffer().toString();
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java
new file mode 100644
index 0000000000..83a36cc5cf
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TemporaryTestDataStorage.java
@@ -0,0 +1,66 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.signer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.ooxml.signature.service.signer.TemporaryDataStorage;
+
+
+
+class TemporaryTestDataStorage implements TemporaryDataStorage {
+
+ private ByteArrayOutputStream outputStream;
+
+ private Map attributes;
+
+ public TemporaryTestDataStorage() {
+ this.outputStream = new ByteArrayOutputStream();
+ this.attributes = new HashMap();
+ }
+
+ public InputStream getTempInputStream() {
+ byte[] data = this.outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ return inputStream;
+ }
+
+ public OutputStream getTempOutputStream() {
+ return this.outputStream;
+ }
+
+ public Serializable getAttribute(String attributeName) {
+ return this.attributes.get(attributeName);
+ }
+
+ public void setAttribute(String attributeName, Serializable attributeValue) {
+ this.attributes.put(attributeName, attributeValue);
+ }
+}
\ No newline at end of file
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java
new file mode 100644
index 0000000000..d6cc51c65a
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractOOXMLSignatureService.java
@@ -0,0 +1,214 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.signer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+import javax.crypto.Cipher;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.poi.ooxml.signature.service.signer.TemporaryDataStorage;
+import org.apache.poi.ooxml.signature.service.signer.ooxml.AbstractOOXMLSignatureService;
+import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider;
+import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier;
+import org.apache.poi.ooxml.signature.service.spi.DigestInfo;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.joda.time.DateTime;
+
+
+
+public class TestAbstractOOXMLSignatureService extends TestCase {
+
+ private static final Log LOG = LogFactory.getLog(TestAbstractOOXMLSignatureService.class);
+
+ static {
+ OOXMLProvider.install();
+ }
+
+ private static class OOXMLTestSignatureService extends AbstractOOXMLSignatureService {
+
+ private final URL ooxmlUrl;
+
+ private final TemporaryTestDataStorage temporaryDataStorage;
+
+ private final ByteArrayOutputStream signedOOXMLOutputStream;
+
+ public OOXMLTestSignatureService(URL ooxmlUrl) {
+ this.temporaryDataStorage = new TemporaryTestDataStorage();
+ this.signedOOXMLOutputStream = new ByteArrayOutputStream();
+ this.ooxmlUrl = ooxmlUrl;
+ }
+
+ @Override
+ protected URL getOfficeOpenXMLDocumentURL() {
+ return this.ooxmlUrl;
+ }
+
+ @Override
+ protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream() {
+ return this.signedOOXMLOutputStream;
+ }
+
+ public byte[] getSignedOfficeOpenXMLDocumentData() {
+ return this.signedOOXMLOutputStream.toByteArray();
+ }
+
+ @Override
+ protected TemporaryDataStorage getTemporaryDataStorage() {
+ return this.temporaryDataStorage;
+ }
+ }
+
+ public void testPreSign() throws Exception {
+ // setup
+ URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource("/hello-world-unsigned.docx");
+ assertNotNull(ooxmlUrl);
+
+ OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl);
+
+ // operate
+ DigestInfo digestInfo = signatureService.preSign(null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ LOG.debug("digest description: " + digestInfo.description);
+ assertEquals("Office OpenXML Document", digestInfo.description);
+ assertNotNull(digestInfo.digestAlgo);
+ assertNotNull(digestInfo.digestValue);
+
+ TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage();
+ String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream());
+ LOG.debug("pre-sign result: " + preSignResult);
+ File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml");
+ FileUtils.writeStringToFile(tmpFile, preSignResult);
+ LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath());
+ }
+
+ public void testPostSign() throws Exception {
+ sign("/hello-world-unsigned.docx");
+ }
+
+ public void testSignOffice2010() throws Exception {
+ sign("/hello-world-office-2010-technical-preview-unsigned.docx");
+ }
+
+ public void testSignTwice() throws Exception {
+ sign("/hello-world-signed.docx", 2);
+ }
+
+ public void testSignTwiceHere() throws Exception {
+ File tmpFile = sign("/hello-world-unsigned.docx", 1);
+ sign(tmpFile.toURI().toURL(), "CN=Test2", 2);
+ }
+
+ public void testSignPowerpoint() throws Exception {
+ sign("/hello-world-unsigned.pptx");
+ }
+
+ public void testSignSpreadsheet() throws Exception {
+ sign("/hello-world-unsigned.xlsx");
+ }
+
+ private void sign(String documentResourceName) throws Exception {
+ sign(documentResourceName, 1);
+ }
+
+ private File sign(String documentResourceName, int signerCount) throws Exception {
+ URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource(documentResourceName);
+ return sign(ooxmlUrl, signerCount);
+ }
+
+ private File sign(URL ooxmlUrl, int signerCount) throws Exception {
+ return sign(ooxmlUrl, "CN=Test", signerCount);
+ }
+
+ private File sign(URL ooxmlUrl, String signerDn, int signerCount) throws Exception {
+ // setup
+ assertNotNull(ooxmlUrl);
+
+ OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl);
+
+ // operate
+ DigestInfo digestInfo = signatureService.preSign(null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ LOG.debug("digest description: " + digestInfo.description);
+ assertEquals("Office OpenXML Document", digestInfo.description);
+ assertNotNull(digestInfo.digestAlgo);
+ assertNotNull(digestInfo.digestValue);
+
+ TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage();
+ String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream());
+ LOG.debug("pre-sign result: " + preSignResult);
+ File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml");
+ FileUtils.writeStringToFile(tmpFile, preSignResult);
+ LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath());
+
+ // setup: key material, signature value
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ DateTime notBefore = new DateTime();
+ DateTime notAfter = notBefore.plusYears(1);
+ X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), signerDn, notBefore, notAfter, null, keyPair.getPrivate(), true, 0,
+ null, null, new KeyUsage(KeyUsage.nonRepudiation));
+
+ // operate: postSign
+ signatureService.postSign(signatureValue, Collections.singletonList(certificate));
+
+ // verify: signature
+ byte[] signedOOXMLData = signatureService.getSignedOfficeOpenXMLDocumentData();
+ assertNotNull(signedOOXMLData);
+ LOG.debug("signed OOXML size: " + signedOOXMLData.length);
+ String extension = FilenameUtils.getExtension(ooxmlUrl.getFile());
+ tmpFile = File.createTempFile("ooxml-signed-", "." + extension);
+ FileUtils.writeByteArrayToFile(tmpFile, signedOOXMLData);
+ LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath());
+ List signers = OOXMLSignatureVerifier.getSigners(tmpFile.toURI().toURL());
+ assertEquals(signerCount, signers.size());
+ // assertEquals(certificate, signers.get(0));
+ LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath());
+ return tmpFile;
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java
new file mode 100644
index 0000000000..c1e474f6e2
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestAbstractXmlSignatureService.java
@@ -0,0 +1,560 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.signer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.Cipher;
+import javax.xml.crypto.Data;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.OctetStreamData;
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.URIReference;
+import javax.xml.crypto.URIReferenceException;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dom.DOMCryptoContext;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.dom.DOMValidateContext;
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.poi.ooxml.signature.service.spi.DigestInfo;
+import org.apache.xml.security.utils.Constants;
+import org.apache.xpath.XPathAPI;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.jcp.xml.dsig.internal.dom.DOMReference;
+import org.jcp.xml.dsig.internal.dom.DOMXMLSignature;
+import org.joda.time.DateTime;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+
+public class TestAbstractXmlSignatureService extends TestCase {
+
+ private static final Log LOG = LogFactory.getLog(TestAbstractXmlSignatureService.class);
+
+ private static class XmlSignatureTestService extends AbstractXmlSignatureService {
+
+ private Document envelopingDocument;
+
+ private List referenceUris;
+
+ private TemporaryTestDataStorage temporaryDataStorage;
+
+ private String signatureDescription;
+
+ private ByteArrayOutputStream signedDocumentOutputStream;
+
+ private URIDereferencer uriDereferencer;
+
+ public XmlSignatureTestService() {
+ super();
+ this.referenceUris = new LinkedList();
+ this.temporaryDataStorage = new TemporaryTestDataStorage();
+ this.signedDocumentOutputStream = new ByteArrayOutputStream();
+ }
+
+ public byte[] getSignedDocumentData() {
+ return this.signedDocumentOutputStream.toByteArray();
+ }
+
+ public void setEnvelopingDocument(Document envelopingDocument) {
+ this.envelopingDocument = envelopingDocument;
+ }
+
+ @Override
+ protected Document getEnvelopingDocument() {
+ return this.envelopingDocument;
+ }
+
+ @Override
+ protected String getSignatureDescription() {
+ return this.signatureDescription;
+ }
+
+ public void setSignatureDescription(String signatureDescription) {
+ this.signatureDescription = signatureDescription;
+ }
+
+ @Override
+ protected List getReferenceUris() {
+ return this.referenceUris;
+ }
+
+ public void addReferenceUri(String referenceUri) {
+ this.referenceUris.add(referenceUri);
+ }
+
+ @Override
+ protected OutputStream getSignedDocumentOutputStream() {
+ return this.signedDocumentOutputStream;
+ }
+
+ @Override
+ protected TemporaryDataStorage getTemporaryDataStorage() {
+ return this.temporaryDataStorage;
+ }
+
+ public String getFilesDigestAlgorithm() {
+ return null;
+ }
+
+ @Override
+ protected URIDereferencer getURIDereferencer() {
+ return this.uriDereferencer;
+ }
+
+ public void setUriDereferencer(URIDereferencer uriDereferencer) {
+ this.uriDereferencer = uriDereferencer;
+ }
+ }
+
+ public void testSignEnvelopingDocument() throws Exception {
+ // setup
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.newDocument();
+ Element rootElement = document.createElementNS("urn:test", "tns:root");
+ rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
+ document.appendChild(rootElement);
+ Element dataElement = document.createElementNS("urn:test", "tns:data");
+ dataElement.setAttributeNS(null, "Id", "id-1234");
+ dataElement.setTextContent("data to be signed");
+ rootElement.appendChild(dataElement);
+
+ XmlSignatureTestService testedInstance = new XmlSignatureTestService();
+ testedInstance.setEnvelopingDocument(document);
+ testedInstance.addReferenceUri("#id-1234");
+ testedInstance.setSignatureDescription("test-signature-description");
+
+ // operate
+ DigestInfo digestInfo = testedInstance.preSign(null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest info description: " + digestInfo.description);
+ assertEquals("test-signature-description", digestInfo.description);
+ assertNotNull(digestInfo.digestValue);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ assertEquals("SHA-1", digestInfo.digestAlgo);
+
+ TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
+ assertNotNull(temporaryDataStorage);
+ InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
+ assertNotNull(tempInputStream);
+ Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
+
+ LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
+ Element nsElement = tmpDocument.createElement("ns");
+ nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
+ Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
+ assertNotNull(digestValueNode);
+ String digestValueTextContent = digestValueNode.getTextContent();
+ LOG.debug("digest value text content: " + digestValueTextContent);
+ assertFalse(digestValueTextContent.isEmpty());
+
+ /*
+ * Sign the received XML signature digest value.
+ */
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ DateTime notBefore = new DateTime();
+ DateTime notAfter = notBefore.plusYears(1);
+ X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
+ 0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
+
+ /*
+ * Operate: postSign
+ */
+ testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
+
+ byte[] signedDocumentData = testedInstance.getSignedDocumentData();
+ assertNotNull(signedDocumentData);
+ Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
+ LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
+
+ NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
+ assertEquals(1, signatureNodeList.getLength());
+ Node signatureNode = signatureNodeList.item(0);
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
+ XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean validity = xmlSignature.validate(domValidateContext);
+ assertTrue(validity);
+ }
+
+ public static class UriTestDereferencer implements URIDereferencer {
+
+ private final Map resources;
+
+ public UriTestDereferencer() {
+ this.resources = new HashMap();
+ }
+
+ public void addResource(String uri, byte[] data) {
+ this.resources.put(uri, data);
+ }
+
+ public Data dereference(URIReference uriReference, XMLCryptoContext xmlCryptoContext) throws URIReferenceException {
+ String uri = uriReference.getURI();
+ byte[] data = this.resources.get(uri);
+ if (null == data) {
+ return null;
+ }
+ return new OctetStreamData(new ByteArrayInputStream(data));
+ }
+ }
+
+ public void testSignExternalUri() throws Exception {
+ // setup
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.newDocument();
+
+ XmlSignatureTestService testedInstance = new XmlSignatureTestService();
+ testedInstance.setEnvelopingDocument(document);
+ testedInstance.addReferenceUri("external-uri");
+ testedInstance.setSignatureDescription("test-signature-description");
+ UriTestDereferencer uriDereferencer = new UriTestDereferencer();
+ uriDereferencer.addResource("external-uri", "hello world".getBytes());
+ testedInstance.setUriDereferencer(uriDereferencer);
+
+ // operate
+ DigestInfo digestInfo = testedInstance.preSign(null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest info description: " + digestInfo.description);
+ assertEquals("test-signature-description", digestInfo.description);
+ assertNotNull(digestInfo.digestValue);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ assertEquals("SHA-1", digestInfo.digestAlgo);
+
+ TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
+ assertNotNull(temporaryDataStorage);
+ InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
+ assertNotNull(tempInputStream);
+ Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
+
+ LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
+ Element nsElement = tmpDocument.createElement("ns");
+ nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
+ Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
+ assertNotNull(digestValueNode);
+ String digestValueTextContent = digestValueNode.getTextContent();
+ LOG.debug("digest value text content: " + digestValueTextContent);
+ assertFalse(digestValueTextContent.isEmpty());
+
+ /*
+ * Sign the received XML signature digest value.
+ */
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ DateTime notBefore = new DateTime();
+ DateTime notAfter = notBefore.plusYears(1);
+ X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
+ 0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
+
+ /*
+ * Operate: postSign
+ */
+ testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
+
+ byte[] signedDocumentData = testedInstance.getSignedDocumentData();
+ assertNotNull(signedDocumentData);
+ Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
+ LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
+
+ NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
+ assertEquals(1, signatureNodeList.getLength());
+ Node signatureNode = signatureNodeList.item(0);
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
+ domValidateContext.setURIDereferencer(uriDereferencer);
+ XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean validity = xmlSignature.validate(domValidateContext);
+ assertTrue(validity);
+ }
+
+ public void testSignEnvelopingDocumentWithExternalDigestInfo() throws Exception {
+ // setup
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.newDocument();
+ Element rootElement = document.createElementNS("urn:test", "tns:root");
+ rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
+ document.appendChild(rootElement);
+
+ XmlSignatureTestService testedInstance = new XmlSignatureTestService();
+ testedInstance.setEnvelopingDocument(document);
+ testedInstance.setSignatureDescription("test-signature-description");
+
+ byte[] refData = "hello world".getBytes();
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+ messageDigest.update(refData);
+ byte[] digestValue = messageDigest.digest();
+ DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref");
+
+ // operate
+ DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest info description: " + digestInfo.description);
+ assertEquals("test-signature-description", digestInfo.description);
+ assertNotNull(digestInfo.digestValue);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ assertEquals("SHA-1", digestInfo.digestAlgo);
+
+ TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
+ assertNotNull(temporaryDataStorage);
+ InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
+ assertNotNull(tempInputStream);
+ Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
+
+ LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
+ Element nsElement = tmpDocument.createElement("ns");
+ nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
+ Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
+ assertNotNull(digestValueNode);
+ String digestValueTextContent = digestValueNode.getTextContent();
+ LOG.debug("digest value text content: " + digestValueTextContent);
+ assertFalse(digestValueTextContent.isEmpty());
+
+ /*
+ * Sign the received XML signature digest value.
+ */
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ DateTime notBefore = new DateTime();
+ DateTime notAfter = notBefore.plusYears(1);
+ X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
+ 0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
+
+ /*
+ * Operate: postSign
+ */
+ testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
+
+ byte[] signedDocumentData = testedInstance.getSignedDocumentData();
+ assertNotNull(signedDocumentData);
+ Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
+ LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
+
+ NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
+ assertEquals(1, signatureNodeList.getLength());
+ Node signatureNode = signatureNodeList.item(0);
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
+ URIDereferencer dereferencer = new URITest2Dereferencer();
+ domValidateContext.setURIDereferencer(dereferencer);
+ XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean validity = xmlSignature.validate(domValidateContext);
+ assertTrue(validity);
+ }
+
+ public void testSignExternalDigestInfo() throws Exception {
+ // setup
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.newDocument();
+
+ XmlSignatureTestService testedInstance = new XmlSignatureTestService();
+ testedInstance.setEnvelopingDocument(document);
+ testedInstance.setSignatureDescription("test-signature-description");
+
+ byte[] refData = "hello world".getBytes();
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+ messageDigest.update(refData);
+ byte[] digestValue = messageDigest.digest();
+ DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref");
+
+ // operate
+ DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.debug("digest info description: " + digestInfo.description);
+ assertEquals("test-signature-description", digestInfo.description);
+ assertNotNull(digestInfo.digestValue);
+ LOG.debug("digest algo: " + digestInfo.digestAlgo);
+ assertEquals("SHA-1", digestInfo.digestAlgo);
+
+ TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage();
+ assertNotNull(temporaryDataStorage);
+ InputStream tempInputStream = temporaryDataStorage.getTempInputStream();
+ assertNotNull(tempInputStream);
+ Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream);
+
+ LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument));
+ Element nsElement = tmpDocument.createElement("ns");
+ nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
+ Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement);
+ assertNotNull(digestValueNode);
+ String digestValueTextContent = digestValueNode.getTextContent();
+ LOG.debug("digest value text content: " + digestValueTextContent);
+ assertFalse(digestValueTextContent.isEmpty());
+
+ /*
+ * Sign the received XML signature digest value.
+ */
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue);
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ DateTime notBefore = new DateTime();
+ DateTime notAfter = notBefore.plusYears(1);
+ X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", notBefore, notAfter, null, keyPair.getPrivate(), true,
+ 0, null, null, new KeyUsage(KeyUsage.nonRepudiation));
+
+ /*
+ * Operate: postSign
+ */
+ testedInstance.postSign(signatureValue, Collections.singletonList(certificate));
+
+ byte[] signedDocumentData = testedInstance.getSignedDocumentData();
+ assertNotNull(signedDocumentData);
+ Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData));
+ LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument));
+
+ NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
+ assertEquals(1, signatureNodeList.getLength());
+ Node signatureNode = signatureNodeList.item(0);
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode);
+ URIDereferencer dereferencer = new URITest2Dereferencer();
+ domValidateContext.setURIDereferencer(dereferencer);
+ XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean validity = xmlSignature.validate(domValidateContext);
+ assertTrue(validity);
+ }
+
+ private static class URITest2Dereferencer implements URIDereferencer {
+
+ private static final Log LOG = LogFactory.getLog(URITest2Dereferencer.class);
+
+ public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException {
+ LOG.debug("dereference: " + uriReference.getURI());
+ return new OctetStreamData(new ByteArrayInputStream("hello world".getBytes()));
+ }
+ }
+
+ public void testJsr105Signature() throws Exception {
+ KeyPair keyPair = PkiTestUtils.generateKeyPair();
+
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ Document document = documentBuilder.newDocument();
+ Element rootElement = document.createElementNS("urn:test", "tns:root");
+ rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test");
+ document.appendChild(rootElement);
+ Element dataElement = document.createElementNS("urn:test", "tns:data");
+ dataElement.setAttributeNS(null, "Id", "id-1234");
+ dataElement.setTextContent("data to be signed");
+ rootElement.appendChild(dataElement);
+
+ XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI());
+
+ XMLSignContext signContext = new DOMSignContext(keyPair.getPrivate(), document.getDocumentElement());
+ signContext.putNamespacePrefix(javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds");
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null);
+ Reference reference = signatureFactory.newReference("#id-1234", digestMethod);
+ DOMReference domReference = (DOMReference) reference;
+ assertNull(domReference.getCalculatedDigestValue());
+ assertNull(domReference.getDigestValue());
+
+ SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
+ CanonicalizationMethod canonicalizationMethod = signatureFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS,
+ (C14NMethodParameterSpec) null);
+ SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));
+
+ javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null);
+
+ DOMXMLSignature domXmlSignature = (DOMXMLSignature) xmlSignature;
+ domXmlSignature.marshal(document.getDocumentElement(), "ds", (DOMCryptoContext) signContext);
+ domReference.digest(signContext);
+ // xmlSignature.sign(signContext);
+ // LOG.debug("signed document: " + toString(document));
+
+ Element nsElement = document.createElement("ns");
+ nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
+ Node digestValueNode = XPathAPI.selectSingleNode(document, "//ds:DigestValue", nsElement);
+ assertNotNull(digestValueNode);
+ String digestValueTextContent = digestValueNode.getTextContent();
+ LOG.debug("digest value text content: " + digestValueTextContent);
+ assertFalse(digestValueTextContent.isEmpty());
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java
new file mode 100644
index 0000000000..9aa79f304d
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ooxml/signature/service/signer/TestOOXMLSignatureVerifier.java
@@ -0,0 +1,238 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/*
+ * Based on the eID Applet Project code.
+ * Original Copyright (C) 2008-2009 FedICT.
+ */
+
+package org.apache.poi.ooxml.signature.service.signer;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.poi.POIXMLDocument;
+import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider;
+import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.signature.PackageDigitalSignatureManager;
+
+
+
+public class TestOOXMLSignatureVerifier extends TestCase {
+
+ private static final Log LOG = LogFactory.getLog(TestOOXMLSignatureVerifier.class);
+
+ static {
+ OOXMLProvider.install();
+ }
+
+ public void testIsOOXMLDocument() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx");
+
+ // operate
+ boolean result = OOXMLSignatureVerifier.isOOXML(url);
+
+ // verify
+ assertTrue(result);
+ }
+
+ public void testPOI() throws Exception {
+ // setup
+ InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-unsigned.docx");
+
+ // operate
+ boolean result = POIXMLDocument.hasOOXMLHeader(inputStream);
+
+ // verify
+ assertTrue(result);
+ }
+
+ public void testOPC() throws Exception {
+ // setup
+ InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-signed.docx");
+
+ // operate
+ OPCPackage opcPackage = OPCPackage.open(inputStream);
+
+ ArrayList parts = opcPackage.getParts();
+ for (PackagePart part : parts) {
+ LOG.debug("part name: " + part.getPartName().getName());
+ LOG.debug("part content type: " + part.getContentType());
+ }
+
+ ArrayList signatureParts = opcPackage.getPartsByContentType("application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml");
+ assertFalse(signatureParts.isEmpty());
+
+ PackagePart signaturePart = signatureParts.get(0);
+ LOG.debug("signature part class type: " + signaturePart.getClass().getName());
+
+ PackageDigitalSignatureManager packageDigitalSignatureManager = new PackageDigitalSignatureManager();
+ // yeah... POI implementation still missing
+ }
+
+ public void testGetSignerUnsigned() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ public void testGetSignerOffice2010Unsigned() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview-unsigned.docx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ public void testGetSignerUnsignedPowerpoint() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.pptx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ public void testGetSignerUnsignedExcel() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.xlsx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ public void testGetSigner() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.debug("signer: " + signer.getSubjectX500Principal());
+ }
+
+ public void testOffice2010TechnicalPreview() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview.docx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.debug("signer: " + signer.getSubjectX500Principal());
+ }
+
+ public void testGetSignerPowerpoint() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.pptx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.debug("signer: " + signer.getSubjectX500Principal());
+ }
+
+ public void testGetSignerExcel() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.xlsx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertEquals(1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.debug("signer: " + signer.getSubjectX500Principal());
+ }
+
+ public void testGetSigners() throws Exception {
+ // setup
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed-twice.docx");
+
+ // operate
+ List result = OOXMLSignatureVerifier.getSigners(url);
+
+ // verify
+ assertNotNull(result);
+ assertEquals(2, result.size());
+ X509Certificate signer1 = result.get(0);
+ X509Certificate signer2 = result.get(1);
+ LOG.debug("signer 1: " + signer1.getSubjectX500Principal());
+ LOG.debug("signer 2: " + signer2.getSubjectX500Principal());
+ }
+
+ public void testVerifySignature() throws Exception {
+
+ java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
+ logger.log(Level.FINE, "test");
+
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx");
+ boolean validity = OOXMLSignatureVerifier.verifySignature(url);
+ assertTrue(validity);
+ }
+
+ public void testTamperedFile() throws Exception {
+
+ java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
+ logger.log(Level.FINE, "test");
+
+ URL url = TestOOXMLSignatureVerifier.class.getResource("/invalidsig.docx");
+ boolean validity = OOXMLSignatureVerifier.verifySignature(url);
+ assertFalse(validity);
+ }
+}