-/* ====================================================================\r
- Licensed to the Apache Software Foundation (ASF) under one or more\r
- contributor license agreements. See the NOTICE file distributed with\r
- this work for additional information regarding copyright ownership.\r
- The ASF licenses this file to You under the Apache License, Version 2.0\r
- (the "License"); you may not use this file except in compliance with\r
- the License. You may obtain a copy of the License at\r
-\r
- http://www.apache.org/licenses/LICENSE-2.0\r
-\r
- Unless required by applicable law or agreed to in writing, software\r
- distributed under the License is distributed on an "AS IS" BASIS,\r
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- See the License for the specific language governing permissions and\r
- limitations under the License.\r
-==================================================================== */\r
-\r
-/* ====================================================================\r
- This product contains an ASLv2 licensed version of the OOXML signer\r
- package from the eID Applet project\r
- http://code.google.com/p/eid-applet/source/browse/trunk/README.txt \r
- Copyright (C) 2008-2014 FedICT.\r
- ================================================================= */ \r
-\r
-package org.apache.poi.poifs.crypt.dsig;\r
-\r
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
import javax.crypto.Cipher;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.URIDereferencer;
-import javax.xml.crypto.XMLStructure;\r
-import javax.xml.crypto.dsig.CanonicalizationMethod;\r
-import javax.xml.crypto.dsig.Manifest;\r
-import javax.xml.crypto.dsig.Reference;\r
-import javax.xml.crypto.dsig.SignatureMethod;\r
-import javax.xml.crypto.dsig.SignedInfo;\r
-import javax.xml.crypto.dsig.XMLObject;\r
-import javax.xml.crypto.dsig.XMLSignContext;\r
-import javax.xml.crypto.dsig.XMLSignature;\r
-import javax.xml.crypto.dsig.XMLSignatureException;\r
-import javax.xml.crypto.dsig.XMLSignatureFactory;\r
-import javax.xml.crypto.dsig.XMLValidateContext;\r
-import javax.xml.crypto.dsig.dom.DOMSignContext;\r
-import javax.xml.crypto.dsig.dom.DOMValidateContext;\r
-import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;\r
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.Manifest;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureException;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.XMLValidateContext;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.dom.DOMValidateContext;
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
-import org.apache.poi.EncryptedDocumentException;\r
-import org.apache.poi.openxml4j.exceptions.InvalidFormatException;\r
-import org.apache.poi.openxml4j.opc.ContentTypes;\r
-import org.apache.poi.openxml4j.opc.OPCPackage;\r
-import org.apache.poi.openxml4j.opc.PackagePart;\r
-import org.apache.poi.openxml4j.opc.PackagePartName;\r
-import org.apache.poi.openxml4j.opc.PackageRelationship;\r
-import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;\r
-import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;\r
-import org.apache.poi.openxml4j.opc.PackagingURIHelper;\r
-import org.apache.poi.openxml4j.opc.TargetMode;\r
-import org.apache.poi.poifs.crypt.ChainingMode;\r
-import org.apache.poi.poifs.crypt.CipherAlgorithm;\r
-import org.apache.poi.poifs.crypt.CryptoFunctions;\r
-import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;\r
-import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;\r
-import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;\r
-import org.apache.poi.util.DocumentHelper;\r
-import org.apache.poi.util.POILogFactory;\r
-import org.apache.poi.util.POILogger;\r
-import org.apache.xml.security.Init;\r
-import org.apache.xml.security.utils.Base64;\r
-import org.apache.xmlbeans.XmlException;\r
-import org.apache.xmlbeans.XmlOptions;\r
-import org.w3.x2000.x09.xmldsig.SignatureDocument;\r
-import org.w3c.dom.Document;\r
-import org.w3c.dom.Element;\r
-import org.w3c.dom.NodeList;\r
-import org.w3c.dom.events.EventListener;\r
-import org.w3c.dom.events.EventTarget;\r
-\r
-\r
-/**\r
- * <p>This class is the default entry point for XML signatures and can be used for\r
- * validating an existing signed office document and signing a office document.</p>\r
- * \r
- * <p><b>Validating a signed office document</b></p>\r
- * \r
- * <pre>\r
- * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ);\r
- * SignatureConfig sic = new SignatureConfig();\r
- * sic.setOpcPackage(pkg);\r
- * SignatureInfo si = new SignatureInfo();\r
- * si.setSignatureConfig(sic);\r
- * boolean isValid = si.validate();\r
- * ...\r
- * </pre>\r
- * \r
- * <p><b>Signing an office document</b></p>\r
- * \r
- * <pre>\r
- * // loading the keystore - pkcs12 is used here, but of course jks & co are also valid\r
- * // the keystore needs to contain a private key and it's certificate having a\r
- * // 'digitalSignature' key usage\r
- * char password[] = "test".toCharArray();\r
- * File file = new File("test.pfx");\r
- * KeyStore keystore = KeyStore.getInstance("PKCS12");\r
- * FileInputStream fis = new FileInputStream(file);\r
- * keystore.load(fis, password);\r
- * fis.close();\r
- * \r
- * // extracting private key and certificate\r
- * String alias = "xyz"; // alias of the keystore entry\r
- * Key key = keystore.getKey(alias, password);\r
- * X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);\r
- * \r
- * // filling the SignatureConfig entries (minimum fields, more options are available ...)\r
- * SignatureConfig signatureConfig = new SignatureConfig();\r
- * signatureConfig.setKey(keyPair.getPrivate());\r
- * signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));\r
- * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);\r
- * signatureConfig.setOpcPackage(pkg);\r
- * \r
- * // adding the signature document to the package\r
- * SignatureInfo si = new SignatureInfo();\r
- * si.setSignatureConfig(signatureConfig);\r
- * si.confirmSignature();\r
- * // optionally verify the generated signature\r
- * boolean b = si.verifySignature();\r
- * assert (b);\r
- * // write the changes back to disc\r
- * pkg.close();\r
- * </pre>\r
- * \r
- * <p><b>Implementation notes:</b></p>\r
- * \r
- * <p>Although there's a XML signature implementation in the Oracle JDKs 6 and higher,\r
- * compatibility with IBM JDKs is also in focus (... but maybe not thoroughly tested ...).\r
- * Therefore we are using the Apache Santuario libs (xmlsec) instead of the built-in classes,\r
- * as the compatibility seems to be provided there.</p>\r
- * \r
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
+import org.apache.poi.util.DocumentHelper;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xml.security.Init;
+import org.apache.xml.security.utils.Base64;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.w3.x2000.x09.xmldsig.SignatureDocument;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+
+
+/**
+ * <p>This class is the default entry point for XML signatures and can be used for
+ * validating an existing signed office document and signing a office document.</p>
+ *
+ * <p><b>Validating a signed office document</b></p>
+ *
+ * <pre>
+ * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ);
+ * SignatureConfig sic = new SignatureConfig();
+ * sic.setOpcPackage(pkg);
+ * SignatureInfo si = new SignatureInfo();
+ * si.setSignatureConfig(sic);
+ * boolean isValid = si.validate();
+ * ...
+ * </pre>
+ *
+ * <p><b>Signing an office document</b></p>
+ *
+ * <pre>
+ * // loading the keystore - pkcs12 is used here, but of course jks & co are also valid
+ * // the keystore needs to contain a private key and it's certificate having a
+ * // 'digitalSignature' key usage
+ * char password[] = "test".toCharArray();
+ * File file = new File("test.pfx");
+ * KeyStore keystore = KeyStore.getInstance("PKCS12");
+ * FileInputStream fis = new FileInputStream(file);
+ * keystore.load(fis, password);
+ * fis.close();
+ *
+ * // extracting private key and certificate
+ * String alias = "xyz"; // alias of the keystore entry
+ * Key key = keystore.getKey(alias, password);
+ * X509Certificate x509 = (X509Certificate)keystore.getCertificate(alias);
+ *
+ * // filling the SignatureConfig entries (minimum fields, more options are available ...)
+ * SignatureConfig signatureConfig = new SignatureConfig();
+ * signatureConfig.setKey(keyPair.getPrivate());
+ * signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
+ * OPCPackage pkg = OPCPackage.open(..., PackageAccess.READ_WRITE);
+ * signatureConfig.setOpcPackage(pkg);
+ *
+ * // adding the signature document to the package
+ * SignatureInfo si = new SignatureInfo();
+ * si.setSignatureConfig(signatureConfig);
+ * si.confirmSignature();
+ * // optionally verify the generated signature
+ * boolean b = si.verifySignature();
+ * assert (b);
+ * // write the changes back to disc
+ * pkg.close();
+ * </pre>
+ *
+ * <p><b>Implementation notes:</b></p>
+ *
+ * <p>Although there's a XML signature implementation in the Oracle JDKs 6 and higher,
+ * compatibility with IBM JDKs is also in focus (... but maybe not thoroughly tested ...).
+ * Therefore we are using the Apache Santuario libs (xmlsec) instead of the built-in classes,
+ * as the compatibility seems to be provided there.</p>
+ *
* <p>To use SignatureInfo and its sibling classes, you'll need to have the following libs
* in the classpath:</p>
* <ul>
* <li>Apache Santuario "xmlsec" (tested against 2.0.5)</li>
* <li>and slf4j-api (tested against 1.7.12)</li>
* </ul>
- */\r
-public class SignatureInfo implements SignatureConfigurable {\r
-\r
- private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);\r
- private static boolean isInitialized = false;\r
- \r
- private SignatureConfig signatureConfig;\r
-\r
- public class SignaturePart {\r
- private final PackagePart signaturePart;\r
- private X509Certificate signer;\r
- private List<X509Certificate> certChain;\r
- \r
- private SignaturePart(PackagePart signaturePart) {\r
- this.signaturePart = signaturePart;\r
- }\r
- \r
- /**\r
- * @return the package part containing the signature\r
- */\r
- public PackagePart getPackagePart() {\r
- return signaturePart;\r
- }\r
- \r
- /**\r
- * @return the signer certificate\r
- */\r
- public X509Certificate getSigner() {\r
- return signer;\r
- }\r
- \r
- /**\r
- * @return the certificate chain of the signer\r
- */\r
- public List<X509Certificate> getCertChain() {\r
- return certChain;\r
- }\r
- \r
- /**\r
- * Helper method for examining the xml signature\r
- *\r
- * @return the xml signature document\r
- * @throws IOException if the xml signature doesn't exist or can't be read\r
- * @throws XmlException if the xml signature is malformed\r
- */\r
- public SignatureDocument getSignatureDocument() throws IOException, XmlException {\r
- // TODO: check for XXE\r
- return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);\r
- }\r
- \r
- /**\r
- * @return true, when the xml signature is valid, false otherwise\r
- * \r
- * @throws EncryptedDocumentException if the signature can't be extracted or if its malformed\r
- */\r
- @SuppressWarnings("unchecked")\r
- public boolean validate() {\r
- KeyInfoKeySelector keySelector = new KeyInfoKeySelector();\r
- try {\r
- Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());\r
- XPath xpath = XPathFactory.newInstance().newXPath();\r
- NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);\r
- for (int i=0; i<nl.getLength(); i++) {\r
- ((Element)nl.item(i)).setIdAttribute("Id", true);\r
- }\r
- \r
- DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);\r
- domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);\r
- domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());\r
- brokenJvmWorkaround(domValidateContext);\r
- \r
- XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();\r
- XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);\r
- \r
- // TODO: replace with property when xml-sec patch is applied\r
- for (Reference ref : (List<Reference>)xmlSignature.getSignedInfo().getReferences()) {\r
- SignatureFacet.brokenJvmWorkaround(ref);\r
- }\r
- for (XMLObject xo : (List<XMLObject>)xmlSignature.getObjects()) {\r
- for (XMLStructure xs : (List<XMLStructure>)xo.getContent()) {\r
- if (xs instanceof Manifest) {\r
- for (Reference ref : (List<Reference>)((Manifest)xs).getReferences()) {\r
- SignatureFacet.brokenJvmWorkaround(ref);\r
- }\r
- }\r
- }\r
- }\r
- \r
- boolean valid = xmlSignature.validate(domValidateContext);\r
-\r
- if (valid) {\r
- signer = keySelector.getSigner();\r
- certChain = keySelector.getCertChain();\r
- }\r
- \r
- return valid;\r
- } catch (Exception e) {\r
- String s = "error in marshalling and validating the signature";\r
- LOG.log(POILogger.ERROR, s, e);\r
- throw new EncryptedDocumentException(s, e);\r
- }\r
- }\r
- }\r
- \r
- /**\r
- * Constructor initializes xml signature environment, if it hasn't been initialized before\r
- */\r
- public SignatureInfo() {\r
- initXmlProvider(); \r
- }\r
- \r
- /**\r
- * @return the signature config\r
- */\r
- public SignatureConfig getSignatureConfig() {\r
- return signatureConfig;\r
- }\r
-\r
- /**\r
- * @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used\r
- */\r
- public void setSignatureConfig(SignatureConfig signatureConfig) {\r
- this.signatureConfig = signatureConfig;\r
- }\r
-\r
- /**\r
- * @return true, if first signature part is valid\r
- */\r
- public boolean verifySignature() {\r
- // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html\r
- for (SignaturePart sp : getSignatureParts()){\r
- // only validate first part\r
- return sp.validate();\r
- }\r
- return false;\r
- }\r
-\r
- /**\r
- * add the xml signature to the document\r
- *\r
- * @throws XMLSignatureException\r
- * @throws MarshalException\r
- */\r
- public void confirmSignature() throws XMLSignatureException, MarshalException {\r
- Document document = DocumentHelper.createDocument();\r
- \r
- // operate\r
- DigestInfo digestInfo = preSign(document, null);\r
-\r
- // setup: key material, signature value\r
- byte[] signatureValue = signDigest(digestInfo.digestValue);\r
- \r
- // operate: postSign\r
- postSign(document, signatureValue);\r
- }\r
-\r
- /**\r
- * Sign (encrypt) the digest with the private key.\r
- * Currently only rsa is supported.\r
- *\r
- * @param digest the hashed input\r
- * @return the encrypted hash\r
- */\r
- public byte[] signDigest(byte digest[]) {\r
- Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa\r
- , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");\r
- \r
- try {\r
- ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();\r
- digestInfoValueBuf.write(signatureConfig.getHashMagic());\r
- digestInfoValueBuf.write(digest);\r
- byte[] digestInfoValue = digestInfoValueBuf.toByteArray();\r
- byte[] signatureValue = cipher.doFinal(digestInfoValue);\r
- return signatureValue;\r
- } catch (Exception e) {\r
- throw new EncryptedDocumentException(e);\r
- }\r
- }\r
- \r
- /**\r
- * @return a signature part for each signature document.\r
- * the parts can be validated independently.\r
- */\r
- public Iterable<SignaturePart> getSignatureParts() {\r
- signatureConfig.init(true);\r
- return new Iterable<SignaturePart>() {\r
- public Iterator<SignaturePart> iterator() {\r
- return new Iterator<SignaturePart>() {\r
- OPCPackage pkg = signatureConfig.getOpcPackage();\r
- Iterator<PackageRelationship> sigOrigRels = \r
- pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator();\r
- Iterator<PackageRelationship> sigRels = null;\r
- PackagePart sigPart = null;\r
- \r
- public boolean hasNext() {\r
- while (sigRels == null || !sigRels.hasNext()) {\r
- if (!sigOrigRels.hasNext()) return false;\r
- sigPart = pkg.getPart(sigOrigRels.next());\r
- LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);\r
- try {\r
- sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE).iterator();\r
- } catch (InvalidFormatException e) {\r
- LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);\r
- }\r
- }\r
- return true;\r
- }\r
- \r
- public SignaturePart next() {\r
- PackagePart sigRelPart = null;\r
- do {\r
- try {\r
- if (!hasNext()) throw new NoSuchElementException();\r
- sigRelPart = sigPart.getRelatedPart(sigRels.next()); \r
- LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);\r
- } catch (InvalidFormatException e) {\r
- LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);\r
- }\r
- } while (sigPart == null);\r
- return new SignaturePart(sigRelPart);\r
- }\r
- \r
- public void remove() {\r
- throw new UnsupportedOperationException();\r
- }\r
- };\r
- }\r
- };\r
- }\r
- \r
- /**\r
- * Initialize the xml signing environment and the bouncycastle provider \r
- */\r
- protected static synchronized void initXmlProvider() {\r
- if (isInitialized) return;\r
- isInitialized = true;\r
- \r
- try {\r
- Init.init();\r
- RelationshipTransformService.registerDsigProvider();\r
- CryptoFunctions.registerBouncyCastle();\r
- } catch (Exception e) {\r
- throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);\r
- }\r
- }\r
- \r
- /**\r
- * Helper method for adding informations before the signing.\r
- * Normally {@link #confirmSignature()} is sufficient to be used.\r
- */\r
- @SuppressWarnings("unchecked")\r
- public DigestInfo preSign(Document document, List<DigestInfo> digestInfos)\r
- throws XMLSignatureException, MarshalException {\r
- signatureConfig.init(false);\r
- \r
- // it's necessary to explicitly set the mdssi namespace, but the sign() method has no\r
- // normal way to interfere with, so we need to add the namespace under the hand ...\r
- EventTarget target = (EventTarget)document;\r
- EventListener creationListener = signatureConfig.getSignatureMarshalListener();\r
- if (creationListener != null) {\r
- if (creationListener instanceof SignatureMarshalListener) {\r
- ((SignatureMarshalListener)creationListener).setEventTarget(target);\r
- }\r
- SignatureMarshalListener.setListener(target, creationListener, true);\r
- }\r
- \r
- /*\r
- * Signature context construction.\r
- */\r
- XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);\r
- URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();\r
- if (null != uriDereferencer) {\r
- xmlSignContext.setURIDereferencer(uriDereferencer);\r
- }\r
-\r
- for (Map.Entry<String,String> me : signatureConfig.getNamespacePrefixes().entrySet()) {\r
- xmlSignContext.putNamespacePrefix(me.getKey(), me.getValue());\r
- }\r
- xmlSignContext.setDefaultNamespacePrefix("");\r
- // signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));\r
- \r
- brokenJvmWorkaround(xmlSignContext);\r
- \r
- XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory();\r
-\r
- /*\r
- * Add ds:References that come from signing client local files.\r
- */\r
- List<Reference> references = new ArrayList<Reference>();\r
- for (DigestInfo digestInfo : safe(digestInfos)) {\r
- byte[] documentDigestValue = digestInfo.digestValue;\r
-\r
- String uri = new File(digestInfo.description).getName();\r
- Reference reference = SignatureFacet.newReference\r
- (uri, null, null, null, documentDigestValue, signatureConfig);\r
- references.add(reference);\r
- }\r
-\r
- /*\r
- * Invoke the signature facets.\r
- */\r
- List<XMLObject> objects = new ArrayList<XMLObject>();\r
- for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {\r
- LOG.log(POILogger.DEBUG, "invoking signature facet: " + signatureFacet.getClass().getSimpleName());\r
- signatureFacet.preSign(document, references, objects);\r
- }\r
-\r
- /*\r
- * ds:SignedInfo\r
- */\r
- SignedInfo signedInfo;\r
- try {\r
- SignatureMethod signatureMethod = signatureFactory.newSignatureMethod\r
- (signatureConfig.getSignatureMethodUri(), null);\r
- CanonicalizationMethod canonicalizationMethod = signatureFactory\r
- .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(),\r
- (C14NMethodParameterSpec) null);\r
- signedInfo = signatureFactory.newSignedInfo(\r
- canonicalizationMethod, signatureMethod, references);\r
- } catch (GeneralSecurityException e) {\r
- throw new XMLSignatureException(e);\r
- }\r
-\r
- /*\r
- * JSR105 ds:Signature creation\r
- */\r
- String signatureValueId = signatureConfig.getPackageSignatureId() + "-signature-value";\r
- javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory\r
- .newXMLSignature(signedInfo, null, objects, signatureConfig.getPackageSignatureId(),\r
- signatureValueId);\r
-\r
- /*\r
- * ds:Signature Marshalling.\r
- */\r
- xmlSignature.sign(xmlSignContext);\r
-\r
- /*\r
- * Completion of undigested ds:References in the ds:Manifests.\r
- */\r
- for (XMLObject object : objects) {\r
- LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName());\r
- List<XMLStructure> objectContentList = object.getContent();\r
- for (XMLStructure objectContent : objectContentList) {\r
- LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());\r
- if (!(objectContent instanceof Manifest)) continue;\r
- Manifest manifest = (Manifest) objectContent;\r
- List<Reference> manifestReferences = manifest.getReferences();\r
- for (Reference manifestReference : manifestReferences) {\r
- if (manifestReference.getDigestValue() != null) continue;\r
-\r
- DOMReference manifestDOMReference = (DOMReference)manifestReference;\r
- manifestDOMReference.digest(xmlSignContext);\r
- }\r
- }\r
- }\r
-\r
- /*\r
- * Completion of undigested ds:References.\r
- */\r
- List<Reference> signedInfoReferences = signedInfo.getReferences();\r
- for (Reference signedInfoReference : signedInfoReferences) {\r
- DOMReference domReference = (DOMReference)signedInfoReference;\r
-\r
- // ds:Reference with external digest value\r
- if (domReference.getDigestValue() != null) continue;\r
- \r
- domReference.digest(xmlSignContext);\r
- }\r
-\r
- /*\r
- * Calculation of XML signature digest value.\r
- */\r
- DOMSignedInfo domSignedInfo = (DOMSignedInfo)signedInfo;\r
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();\r
- domSignedInfo.canonicalize(xmlSignContext, dataStream);\r
- byte[] octets = dataStream.toByteArray();\r
-\r
- /*\r
- * TODO: we could be using DigestOutputStream here to optimize memory\r
- * usage.\r
- */\r
-\r
- MessageDigest md = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo());\r
- byte[] digestValue = md.digest(octets);\r
- \r
- \r
- String description = signatureConfig.getSignatureDescription();\r
- return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description);\r
- }\r
-\r
- /**\r
- * Helper method for adding informations after the signing.\r
- * Normally {@link #confirmSignature()} is sufficient to be used.\r
- */\r
- public void postSign(Document document, byte[] signatureValue)\r
- throws MarshalException {\r
- LOG.log(POILogger.DEBUG, "postSign");\r
-\r
- /*\r
- * Check ds:Signature node.\r
- */\r
- String signatureId = signatureConfig.getPackageSignatureId();\r
- if (!signatureId.equals(document.getDocumentElement().getAttribute("Id"))) {\r
- throw new RuntimeException("ds:Signature not found for @Id: " + signatureId);\r
- }\r
-\r
- /*\r
- * Insert signature value into the ds:SignatureValue element\r
- */\r
- NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");\r
- if (sigValNl.getLength() != 1) {\r
- throw new RuntimeException("preSign has to be called before postSign");\r
- }\r
- sigValNl.item(0).setTextContent(Base64.encode(signatureValue));\r
-\r
- /*\r
- * Allow signature facets to inject their own stuff.\r
- */\r
- for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {\r
- signatureFacet.postSign(document);\r
- }\r
-\r
- writeDocument(document);\r
- }\r
-\r
- /**\r
- * Write XML signature into the OPC package\r
- *\r
- * @param document the xml signature document\r
- * @throws MarshalException\r
- */\r
- protected void writeDocument(Document document) throws MarshalException {\r
- XmlOptions xo = new XmlOptions();\r
- Map<String,String> namespaceMap = new HashMap<String,String>();\r
- for(Map.Entry<String,String> entry : signatureConfig.getNamespacePrefixes().entrySet()){\r
- namespaceMap.put(entry.getValue(), entry.getKey());\r
- } \r
- xo.setSaveSuggestedPrefixes(namespaceMap);\r
- xo.setUseDefaultNamespace();\r
-\r
- LOG.log(POILogger.DEBUG, "output signed Office OpenXML document");\r
-\r
- /*\r
- * Copy the original OOXML content to the signed OOXML package. During\r
- * copying some files need to changed.\r
- */\r
- OPCPackage pkg = signatureConfig.getOpcPackage();\r
-\r
- PackagePartName sigPartName, sigsPartName;\r
- try {\r
- // <Override PartName="/_xmlsignatures/sig1.xml" ContentType="application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"/>\r
- sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");\r
- // <Default Extension="sigs" ContentType="application/vnd.openxmlformats-package.digital-signature-origin"/>\r
- sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs");\r
- } catch (InvalidFormatException e) {\r
- throw new MarshalException(e);\r
- }\r
- \r
- PackagePart sigPart = pkg.getPart(sigPartName);\r
- if (sigPart == null) {\r
- sigPart = pkg.createPart(sigPartName, ContentTypes.DIGITAL_SIGNATURE_XML_SIGNATURE_PART);\r
- }\r
- \r
- try {\r
- OutputStream os = sigPart.getOutputStream();\r
- SignatureDocument sigDoc = SignatureDocument.Factory.parse(document, DEFAULT_XML_OPTIONS);\r
- sigDoc.save(os, xo);\r
- os.close();\r
- } catch (Exception e) {\r
- throw new MarshalException("Unable to write signature document", e);\r
- }\r
- \r
- PackagePart sigsPart = pkg.getPart(sigsPartName);\r
- if (sigsPart == null) {\r
- // touch empty marker file\r
- sigsPart = pkg.createPart(sigsPartName, ContentTypes.DIGITAL_SIGNATURE_ORIGIN_PART);\r
- }\r
- \r
- PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);\r
- for (PackageRelationship pr : relCol) {\r
- pkg.removeRelationship(pr.getId());\r
- }\r
- pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);\r
- \r
- sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);\r
- }\r
- \r
- /**\r
- * Helper method for null lists, which are converted to empty lists\r
- *\r
- * @param other the reference to wrap, if null\r
- * @return if other is null, an empty lists is returned, otherwise other is returned\r
- */\r
- private static <T> List<T> safe(List<T> other) {\r
- List<T> emptyList = Collections.emptyList();\r
- return other == null ? emptyList : other;\r
- }\r
-\r
- private void brokenJvmWorkaround(XMLSignContext context) {\r
- // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012\r
- Provider bcProv = Security.getProvider("BC");\r
- if (bcProv != null) {\r
- context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);\r
- } \r
- }\r
-\r
- private void brokenJvmWorkaround(XMLValidateContext context) {\r
- // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012\r
- Provider bcProv = Security.getProvider("BC");\r
- if (bcProv != null) {\r
- context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);\r
- } \r
- }\r
-}\r
+ */
+public class SignatureInfo implements SignatureConfigurable {
+
+ private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
+ private static boolean isInitialized = false;
+
+ private SignatureConfig signatureConfig;
+
+ public class SignaturePart {
+ private final PackagePart signaturePart;
+ private X509Certificate signer;
+ private List<X509Certificate> certChain;
+
+ private SignaturePart(PackagePart signaturePart) {
+ this.signaturePart = signaturePart;
+ }
+
+ /**
+ * @return the package part containing the signature
+ */
+ public PackagePart getPackagePart() {
+ return signaturePart;
+ }
+
+ /**
+ * @return the signer certificate
+ */
+ public X509Certificate getSigner() {
+ return signer;
+ }
+
+ /**
+ * @return the certificate chain of the signer
+ */
+ public List<X509Certificate> getCertChain() {
+ return certChain;
+ }
+
+ /**
+ * Helper method for examining the xml signature
+ *
+ * @return the xml signature document
+ * @throws IOException if the xml signature doesn't exist or can't be read
+ * @throws XmlException if the xml signature is malformed
+ */
+ public SignatureDocument getSignatureDocument() throws IOException, XmlException {
+ // TODO: check for XXE
+ return SignatureDocument.Factory.parse(signaturePart.getInputStream(), DEFAULT_XML_OPTIONS);
+ }
+
+ /**
+ * @return true, when the xml signature is valid, false otherwise
+ *
+ * @throws EncryptedDocumentException if the signature can't be extracted or if its malformed
+ */
+ @SuppressWarnings("unchecked")
+ public boolean validate() {
+ KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
+ try {
+ Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
+ for (int i=0; i<nl.getLength(); i++) {
+ ((Element)nl.item(i)).setIdAttribute("Id", true);
+ }
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
+ domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
+ domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
+ brokenJvmWorkaround(domValidateContext);
+
+ XMLSignatureFactory xmlSignatureFactory = signatureConfig.getSignatureFactory();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+
+ // TODO: replace with property when xml-sec patch is applied
+ for (Reference ref : (List<Reference>)xmlSignature.getSignedInfo().getReferences()) {
+ SignatureFacet.brokenJvmWorkaround(ref);
+ }
+ for (XMLObject xo : (List<XMLObject>)xmlSignature.getObjects()) {
+ for (XMLStructure xs : (List<XMLStructure>)xo.getContent()) {
+ if (xs instanceof Manifest) {
+ for (Reference ref : (List<Reference>)((Manifest)xs).getReferences()) {
+ SignatureFacet.brokenJvmWorkaround(ref);
+ }
+ }
+ }
+ }
+
+ boolean valid = xmlSignature.validate(domValidateContext);
+
+ if (valid) {
+ signer = keySelector.getSigner();
+ certChain = keySelector.getCertChain();
+ }
+
+ return valid;
+ } catch (Exception e) {
+ String s = "error in marshalling and validating the signature";
+ LOG.log(POILogger.ERROR, s, e);
+ throw new EncryptedDocumentException(s, e);
+ }
+ }
+ }
+
+ /**
+ * Constructor initializes xml signature environment, if it hasn't been initialized before
+ */
+ public SignatureInfo() {
+ initXmlProvider();
+ }
+
+ /**
+ * @return the signature config
+ */
+ public SignatureConfig getSignatureConfig() {
+ return signatureConfig;
+ }
+
+ /**
+ * @param signatureConfig the signature config, needs to be set before a SignatureInfo object is used
+ */
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ /**
+ * @return true, if first signature part is valid
+ */
+ public boolean verifySignature() {
+ // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
+ for (SignaturePart sp : getSignatureParts()){
+ // only validate first part
+ return sp.validate();
+ }
+ return false;
+ }
+
+ /**
+ * add the xml signature to the document
+ *
+ * @throws XMLSignatureException
+ * @throws MarshalException
+ */
+ public void confirmSignature() throws XMLSignatureException, MarshalException {
+ Document document = DocumentHelper.createDocument();
+
+ // operate
+ DigestInfo digestInfo = preSign(document, null);
+
+ // setup: key material, signature value
+ byte[] signatureValue = signDigest(digestInfo.digestValue);
+
+ // operate: postSign
+ postSign(document, signatureValue);
+ }
+
+ /**
+ * Sign (encrypt) the digest with the private key.
+ * Currently only rsa is supported.
+ *
+ * @param digest the hashed input
+ * @return the encrypted hash
+ */
+ public byte[] signDigest(byte digest[]) {
+ Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa
+ , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
+
+ try {
+ ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
+ digestInfoValueBuf.write(signatureConfig.getHashMagic());
+ digestInfoValueBuf.write(digest);
+ byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+ return signatureValue;
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+ /**
+ * @return a signature part for each signature document.
+ * the parts can be validated independently.
+ */
+ public Iterable<SignaturePart> getSignatureParts() {
+ signatureConfig.init(true);
+ return new Iterable<SignaturePart>() {
+ public Iterator<SignaturePart> iterator() {
+ return new Iterator<SignaturePart>() {
+ OPCPackage pkg = signatureConfig.getOpcPackage();
+ Iterator<PackageRelationship> sigOrigRels =
+ pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator();
+ Iterator<PackageRelationship> sigRels = null;
+ PackagePart sigPart = null;
+
+ public boolean hasNext() {
+ while (sigRels == null || !sigRels.hasNext()) {
+ if (!sigOrigRels.hasNext()) return false;
+ sigPart = pkg.getPart(sigOrigRels.next());
+ LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
+ try {
+ sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE).iterator();
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
+ }
+ }
+ return true;
+ }
+
+ public SignaturePart next() {
+ PackagePart sigRelPart = null;
+ do {
+ try {
+ if (!hasNext()) throw new NoSuchElementException();
+ sigRelPart = sigPart.getRelatedPart(sigRels.next());
+ LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
+ }
+ } while (sigPart == null);
+ return new SignaturePart(sigRelPart);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Initialize the xml signing environment and the bouncycastle provider
+ */
+ protected static synchronized void initXmlProvider() {
+ if (isInitialized) return;
+ isInitialized = true;
+
+ try {
+ Init.init();
+ RelationshipTransformService.registerDsigProvider();
+ CryptoFunctions.registerBouncyCastle();
+ } catch (Exception e) {
+ throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
+ }
+ }
+
+ /**
+ * Helper method for adding informations before the signing.
+ * Normally {@link #confirmSignature()} is sufficient to be used.
+ */
+ @SuppressWarnings("unchecked")
+ public DigestInfo preSign(Document document, List<DigestInfo> digestInfos)
+ throws XMLSignatureException, MarshalException {
+ signatureConfig.init(false);
+
+ // it's necessary to explicitly set the mdssi namespace, but the sign() method has no
+ // normal way to interfere with, so we need to add the namespace under the hand ...
+ EventTarget target = (EventTarget)document;
+ EventListener creationListener = signatureConfig.getSignatureMarshalListener();
+ if (creationListener != null) {
+ if (creationListener instanceof SignatureMarshalListener) {
+ ((SignatureMarshalListener)creationListener).setEventTarget(target);
+ }
+ SignatureMarshalListener.setListener(target, creationListener, true);
+ }
+
+ /*
+ * Signature context construction.
+ */
+ XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
+ URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
+ if (null != uriDereferencer) {
+ xmlSignContext.setURIDereferencer(uriDereferencer);
+ }
+
+ for (Map.Entry<String,String> me : signatureConfig.getNamespacePrefixes().entrySet()) {
+ xmlSignContext.putNamespacePrefix(me.getKey(), me.getValue());
+ }
+ xmlSignContext.setDefaultNamespacePrefix("");
+ // signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));
+
+ brokenJvmWorkaround(xmlSignContext);
+
+ XMLSignatureFactory signatureFactory = signatureConfig.getSignatureFactory();
+
+ /*
+ * Add ds:References that come from signing client local files.
+ */
+ List<Reference> references = new ArrayList<Reference>();
+ for (DigestInfo digestInfo : safe(digestInfos)) {
+ byte[] documentDigestValue = digestInfo.digestValue;
+
+ String uri = new File(digestInfo.description).getName();
+ Reference reference = SignatureFacet.newReference
+ (uri, null, null, null, documentDigestValue, signatureConfig);
+ references.add(reference);
+ }
+
+ /*
+ * Invoke the signature facets.
+ */
+ List<XMLObject> objects = new ArrayList<XMLObject>();
+ for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {
+ LOG.log(POILogger.DEBUG, "invoking signature facet: " + signatureFacet.getClass().getSimpleName());
+ signatureFacet.preSign(document, references, objects);
+ }
+
+ /*
+ * ds:SignedInfo
+ */
+ SignedInfo signedInfo;
+ try {
+ SignatureMethod signatureMethod = signatureFactory.newSignatureMethod
+ (signatureConfig.getSignatureMethodUri(), null);
+ CanonicalizationMethod canonicalizationMethod = signatureFactory
+ .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(),
+ (C14NMethodParameterSpec) null);
+ signedInfo = signatureFactory.newSignedInfo(
+ canonicalizationMethod, signatureMethod, references);
+ } catch (GeneralSecurityException e) {
+ throw new XMLSignatureException(e);
+ }
+
+ /*
+ * JSR105 ds:Signature creation
+ */
+ String signatureValueId = signatureConfig.getPackageSignatureId() + "-signature-value";
+ javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory
+ .newXMLSignature(signedInfo, null, objects, signatureConfig.getPackageSignatureId(),
+ signatureValueId);
+
+ /*
+ * ds:Signature Marshalling.
+ */
+ xmlSignature.sign(xmlSignContext);
+
+ /*
+ * Completion of undigested ds:References in the ds:Manifests.
+ */
+ for (XMLObject object : objects) {
+ LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName());
+ List<XMLStructure> objectContentList = object.getContent();
+ for (XMLStructure objectContent : objectContentList) {
+ LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());
+ if (!(objectContent instanceof Manifest)) continue;
+ Manifest manifest = (Manifest) objectContent;
+ List<Reference> manifestReferences = manifest.getReferences();
+ for (Reference manifestReference : manifestReferences) {
+ if (manifestReference.getDigestValue() != null) continue;
+
+ DOMReference manifestDOMReference = (DOMReference)manifestReference;
+ manifestDOMReference.digest(xmlSignContext);
+ }
+ }
+ }
+
+ /*
+ * Completion of undigested ds:References.
+ */
+ List<Reference> signedInfoReferences = signedInfo.getReferences();
+ for (Reference signedInfoReference : signedInfoReferences) {
+ DOMReference domReference = (DOMReference)signedInfoReference;
+
+ // ds:Reference with external digest value
+ if (domReference.getDigestValue() != null) continue;
+
+ domReference.digest(xmlSignContext);
+ }
+
+ /*
+ * Calculation of XML signature digest value.
+ */
+ DOMSignedInfo domSignedInfo = (DOMSignedInfo)signedInfo;
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ domSignedInfo.canonicalize(xmlSignContext, dataStream);
+ byte[] octets = dataStream.toByteArray();
+
+ /*
+ * TODO: we could be using DigestOutputStream here to optimize memory
+ * usage.
+ */
+
+ MessageDigest md = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo());
+ byte[] digestValue = md.digest(octets);
+
+
+ String description = signatureConfig.getSignatureDescription();
+ return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description);
+ }
+
+ /**
+ * Helper method for adding informations after the signing.
+ * Normally {@link #confirmSignature()} is sufficient to be used.
+ */
+ public void postSign(Document document, byte[] signatureValue)
+ throws MarshalException {
+ LOG.log(POILogger.DEBUG, "postSign");
+
+ /*
+ * Check ds:Signature node.
+ */
+ String signatureId = signatureConfig.getPackageSignatureId();
+ if (!signatureId.equals(document.getDocumentElement().getAttribute("Id"))) {
+ throw new RuntimeException("ds:Signature not found for @Id: " + signatureId);
+ }
+
+ /*
+ * Insert signature value into the ds:SignatureValue element
+ */
+ NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
+ if (sigValNl.getLength() != 1) {
+ throw new RuntimeException("preSign has to be called before postSign");
+ }
+ sigValNl.item(0).setTextContent(Base64.encode(signatureValue));
+
+ /*
+ * Allow signature facets to inject their own stuff.
+ */
+ for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {
+ signatureFacet.postSign(document);
+ }
+
+ writeDocument(document);
+ }
+
+ /**
+ * Write XML signature into the OPC package
+ *
+ * @param document the xml signature document
+ * @throws MarshalException
+ */
+ protected void writeDocument(Document document) throws MarshalException {
+ XmlOptions xo = new XmlOptions();
+ Map<String,String> namespaceMap = new HashMap<String,String>();
+ for(Map.Entry<String,String> entry : signatureConfig.getNamespacePrefixes().entrySet()){
+ namespaceMap.put(entry.getValue(), entry.getKey());
+ }
+ xo.setSaveSuggestedPrefixes(namespaceMap);
+ xo.setUseDefaultNamespace();
+
+ LOG.log(POILogger.DEBUG, "output signed Office OpenXML document");
+
+ /*
+ * Copy the original OOXML content to the signed OOXML package. During
+ * copying some files need to changed.
+ */
+ OPCPackage pkg = signatureConfig.getOpcPackage();
+
+ PackagePartName sigPartName, sigsPartName;
+ try {
+ // <Override PartName="/_xmlsignatures/sig1.xml" ContentType="application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"/>
+ sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");
+ // <Default Extension="sigs" ContentType="application/vnd.openxmlformats-package.digital-signature-origin"/>
+ sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs");
+ } catch (InvalidFormatException e) {
+ throw new MarshalException(e);
+ }
+
+ PackagePart sigPart = pkg.getPart(sigPartName);
+ if (sigPart == null) {
+ sigPart = pkg.createPart(sigPartName, ContentTypes.DIGITAL_SIGNATURE_XML_SIGNATURE_PART);
+ }
+
+ try {
+ OutputStream os = sigPart.getOutputStream();
+ SignatureDocument sigDoc = SignatureDocument.Factory.parse(document, DEFAULT_XML_OPTIONS);
+ sigDoc.save(os, xo);
+ os.close();
+ } catch (Exception e) {
+ throw new MarshalException("Unable to write signature document", e);
+ }
+
+ PackagePart sigsPart = pkg.getPart(sigsPartName);
+ if (sigsPart == null) {
+ // touch empty marker file
+ sigsPart = pkg.createPart(sigsPartName, ContentTypes.DIGITAL_SIGNATURE_ORIGIN_PART);
+ }
+
+ PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+ for (PackageRelationship pr : relCol) {
+ pkg.removeRelationship(pr.getId());
+ }
+ pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+
+ sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
+ }
+
+ /**
+ * Helper method for null lists, which are converted to empty lists
+ *
+ * @param other the reference to wrap, if null
+ * @return if other is null, an empty lists is returned, otherwise other is returned
+ */
+ private static <T> List<T> safe(List<T> other) {
+ List<T> emptyList = Collections.emptyList();
+ return other == null ? emptyList : other;
+ }
+
+ private void brokenJvmWorkaround(XMLSignContext context) {
+ // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
+ Provider bcProv = Security.getProvider("BC");
+ if (bcProv != null) {
+ context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
+ }
+ }
+
+ private void brokenJvmWorkaround(XMLValidateContext context) {
+ // workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1155012
+ Provider bcProv = Security.getProvider("BC");
+ if (bcProv != null) {
+ context.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", bcProv);
+ }
+ }
+}