From 1fd5289bf6bfa14ddb170c276bf0f733257cf288 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Thu, 18 Sep 2014 23:47:41 +0000 Subject: [PATCH] reorganization, xmlsignatureservice is now in signatureinfo git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xml_signature@1626107 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/poifs/crypt/dsig/SignatureInfo.java | 420 ++++++++++++--- .../poifs/crypt/dsig/SignatureInfoConfig.java | 51 +- .../dsig/facets/EnvelopedSignatureFacet.java | 36 +- .../dsig/facets/KeyInfoSignatureFacet.java | 11 +- .../dsig/facets/OOXMLSignatureFacet.java | 33 +- .../dsig/facets/Office2010SignatureFacet.java | 11 +- .../crypt/dsig/facets/SignatureFacet.java | 2 - .../dsig/facets/XAdESSignatureFacet.java | 32 +- .../dsig/facets/XAdESXLSignatureFacet.java | 8 +- .../SignaturePolicyService.java | 2 +- .../crypt/dsig/services/SignatureService.java | 68 --- .../dsig/services/XmlSignatureService.java | 481 ------------------ .../poi/poifs/crypt/TestSignatureInfo.java | 50 +- 13 files changed, 496 insertions(+), 709 deletions(-) rename src/ooxml/java/org/apache/poi/poifs/crypt/dsig/{facets => services}/SignaturePolicyService.java (95%) delete mode 100644 src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java delete mode 100644 src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index 963d023c4a..038c4306dc 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@ -24,48 +24,88 @@ package org.apache.poi.poifs.crypt.dsig; +import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160; +import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1; +import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256; +import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384; +import static org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512; + import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.security.InvalidAlgorithmParameterException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; +import java.security.NoSuchProviderException; import java.security.Provider; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.crypto.Cipher; import javax.xml.crypto.MarshalException; +import javax.xml.crypto.URIDereferencer; +import javax.xml.crypto.XMLStructure; +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.crypto.dsig.DigestMethod; +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.dom.DOMSignContext; import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; +import org.apache.jcp.xml.dsig.internal.dom.DOMReference; +import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo; import org.apache.poi.EncryptedDocumentException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageNamespaces; 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.HashAlgorithm; +import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService; -import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService; import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; 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.xmlbeans.XmlCursor; +import org.apache.xml.security.utils.Base64; import org.apache.xmlbeans.XmlException; -import org.apache.xmlbeans.XmlObject; +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.Node; import org.w3c.dom.NodeList; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; +import org.w3c.dom.events.MutationEvent; +import org.xml.sax.SAXException; public class SignatureInfo { @@ -104,12 +144,16 @@ public class SignatureInfo { private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class); private static boolean isInitialized = false; - private final OPCPackage pkg; - - public SignatureInfo(OPCPackage pkg) { - this.pkg = pkg; + private SignatureInfoConfig signatureConfig; + + public SignatureInfoConfig getSignatureConfig() { + return signatureConfig; } - + + public void setSignatureConfig(SignatureInfoConfig signatureConfig) { + this.signatureConfig = signatureConfig; + } + public boolean verifySignature() { initXmlProvider(); // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html @@ -117,40 +161,27 @@ public class SignatureInfo { return getSignersAndValidate(signers, true); } - public void confirmSignature(PrivateKey key, X509Certificate x509) - throws NoSuchAlgorithmException, IOException, MarshalException, ParserConfigurationException, XmlException { - confirmSignature(key, x509, HashAlgorithm.sha1); - } - - public void confirmSignature(PrivateKey key, X509Certificate x509, HashAlgorithm hashAlgo) - throws NoSuchAlgorithmException, IOException, MarshalException, ParserConfigurationException, XmlException { - SignatureInfoConfig signatureConfig = new SignatureInfoConfig(); - signatureConfig.setOpcPackage(pkg); - signatureConfig.setDigestAlgo(hashAlgo); - signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); - signatureConfig.setKey(key); - signatureConfig.addDefaultFacets(); - XmlSignatureService signatureService = new XmlSignatureService(signatureConfig); - + public void confirmSignature() + throws NoSuchAlgorithmException, IOException, MarshalException, ParserConfigurationException, XmlException, InvalidAlgorithmParameterException, NoSuchProviderException, XMLSignatureException, TransformerFactoryConfigurationError, TransformerException, SAXException, URISyntaxException { Document document = DocumentHelper.createDocument(); // operate - DigestInfo digestInfo = signatureService.preSign(document, null); + DigestInfo digestInfo = preSign(document, null); // setup: key material, signature value - byte[] signatureValue = signDigest(key, hashAlgo, digestInfo.digestValue); + byte[] signatureValue = signDigest(digestInfo.digestValue); // operate: postSign - signatureService.postSign(document, signatureValue); + postSign(document, signatureValue); } - public static byte[] signDigest(PrivateKey key, HashAlgorithm hashAlgo, byte digest[]) { - Cipher cipher = CryptoFunctions.getCipher(key, CipherAlgorithm.rsa + 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(getHashMagic(hashAlgo)); + digestInfoValueBuf.write(getHashMagic()); digestInfoValueBuf.write(digest); byte[] digestInfoValue = digestInfoValueBuf.toByteArray(); byte[] signatureValue = cipher.doFinal(digestInfoValue); @@ -175,15 +206,12 @@ public class SignatureInfo { allValid = false; } - SignatureInfoConfig signatureConfig = new SignatureInfoConfig(); - signatureConfig.setOpcPackage(pkg); - for (PackagePart signaturePart : signatureParts) { KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); try { Document doc = DocumentHelper.readDocument(signaturePart.getInputStream()); - XmlSignatureService.registerIds(doc); + registerIds(doc); DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc); domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); @@ -209,6 +237,7 @@ public class SignatureInfo { protected List getSignatureParts(boolean onlyFirst) { List packageParts = new ArrayList(); + OPCPackage pkg = signatureConfig.getOpcPackage(); PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN); for (PackageRelationship rel : sigOrigRels) { @@ -260,31 +289,6 @@ public class SignatureInfo { throw new RuntimeException("JRE doesn't support default xml signature provider - set jsr105Provider system property!"); } - public static void insertXChild(XmlObject root, XmlObject child) { - XmlCursor rootCursor = root.newCursor(); - insertXChild(rootCursor, child); - rootCursor.dispose(); - } - - public static void insertXChild(XmlCursor rootCursor, XmlObject child) { - rootCursor.toEndToken(); - XmlCursor childCursor = child.newCursor(); - childCursor.toNextToken(); - childCursor.moveXml(rootCursor); - childCursor.dispose(); - } - -// public static void setPrefix(XmlObject xobj, String ns, String prefix) { -// XmlCursor cur; -// for (cur = xobj.newCursor(); cur.hasNextToken(); cur.toNextToken()) { -// if (cur.isStart()) { -// Element el = (Element)cur.getDomNode(); -// if (ns.equals(el.getNamespaceURI())) el.setPrefix(prefix); -// } -// } -// cur.dispose(); -// } - public static void setPrefix(Node el, String ns, String prefix) { if (ns.equals(el.getNamespaceURI())) el.setPrefix(prefix); NodeList nl = el.getChildNodes(); @@ -293,8 +297,8 @@ public class SignatureInfo { } } - protected static byte[] getHashMagic(HashAlgorithm hashAlgo) { - switch (hashAlgo) { + protected byte[] getHashMagic() { + switch (signatureConfig.getDigestAlgo()) { case sha1: return SHA1_DIGEST_INFO_PREFIX; // sha224: return SHA224_DIGEST_INFO_PREFIX; case sha256: return SHA256_DIGEST_INFO_PREFIX; @@ -303,9 +307,22 @@ public class SignatureInfo { case ripemd128: return RIPEMD128_DIGEST_INFO_PREFIX; case ripemd160: return RIPEMD160_DIGEST_INFO_PREFIX; // case ripemd256: return RIPEMD256_DIGEST_INFO_PREFIX; - default: throw new EncryptedDocumentException("Hash algorithm "+hashAlgo+" not supported for signing."); + default: throw new EncryptedDocumentException("Hash algorithm "+signatureConfig.getDigestAlgo()+" not supported for signing."); } } + + protected String getSignatureMethod() { + switch (signatureConfig.getDigestAlgo()) { + case sha1: return ALGO_ID_SIGNATURE_RSA_SHA1; + case sha256: return ALGO_ID_SIGNATURE_RSA_SHA256; + case sha384: return ALGO_ID_SIGNATURE_RSA_SHA384; + case sha512: return ALGO_ID_SIGNATURE_RSA_SHA512; + case ripemd160: return ALGO_ID_MAC_HMAC_RIPEMD160; + default: throw new EncryptedDocumentException("Hash algorithm "+signatureConfig.getDigestAlgo()+" not supported for signing."); + } + } + + public static synchronized void initXmlProvider() { if (isInitialized) return; @@ -319,4 +336,281 @@ public class SignatureInfo { throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e); } } + + @SuppressWarnings("unchecked") + public DigestInfo preSign(Document document, List digestInfos) + throws ParserConfigurationException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException, MarshalException, + javax.xml.crypto.dsig.XMLSignatureException, + TransformerFactoryConfigurationError, TransformerException, + IOException, SAXException, NoSuchProviderException, XmlException, URISyntaxException { + SignatureInfo.initXmlProvider(); + + // 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 ... + final EventTarget et = (EventTarget)document; + EventListener myModificationListener = new EventListener() { + @Override + public void handleEvent(Event e) { + if (e instanceof MutationEvent) { + MutationEvent mutEvt = (MutationEvent)e; + if (mutEvt.getTarget() instanceof Element) { + Element el = (Element)mutEvt.getTarget(); + if ("idPackageObject".equals(el.getAttribute("Id"))) { + et.removeEventListener("DOMSubtreeModified", this, false); + el.setAttributeNS(XmlNS, "xmlns:mdssi", PackageNamespaces.DIGITAL_SIGNATURE); + } + } + } + } + }; + + et.addEventListener("DOMSubtreeModified", myModificationListener, false); + + /* + * Signature context construction. + */ + XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document); + URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer(); + if (null != uriDereferencer) { + xmlSignContext.setURIDereferencer(uriDereferencer); + } + + xmlSignContext.putNamespacePrefix( + "http://schemas.openxmlformats.org/package/2006/digital-signature", + "mdssi"); + + String sigNsPrefix = signatureConfig.getSignatureNamespacePrefix(); + if (sigNsPrefix != null) { + /* + * OOo doesn't like ds namespaces so per default prefixing is off. + */ + xmlSignContext.putNamespacePrefix(XmlDSigNS, sigNsPrefix); + } + + XMLSignatureFactory signatureFactory = SignatureInfo.getSignatureFactory(); + + /* + * Add ds:References that come from signing client local files. + */ + List references = new ArrayList(); + for (DigestInfo digestInfo : safe(digestInfos)) { + byte[] documentDigestValue = digestInfo.digestValue; + + DigestMethod digestMethod = signatureFactory.newDigestMethod( + digestInfo.hashAlgo.xmlSignUri, null); + + String uri = new File(digestInfo.description).getName(); + + Reference reference = signatureFactory.newReference + (uri, digestMethod, null, null, null, documentDigestValue); + references.add(reference); + } + + /* + * Invoke the signature facets. + */ + List objects = new ArrayList(); + for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) { + LOG.log(POILogger.DEBUG, "invoking signature facet: " + signatureFacet.getClass().getSimpleName()); + signatureFacet.preSign(document, signatureFactory, references, objects); + } + + /* + * ds:SignedInfo + */ + SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(getSignatureMethod(), null); + CanonicalizationMethod canonicalizationMethod = signatureFactory + .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(), + (C14NMethodParameterSpec) null); + SignedInfo signedInfo = signatureFactory.newSignedInfo( + canonicalizationMethod, signatureMethod, references); + + /* + * 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. + */ + xmlSignContext.setDefaultNamespacePrefix(signatureConfig.getSignatureNamespacePrefix()); + // xmlSignContext.putNamespacePrefix(PackageNamespaces.DIGITAL_SIGNATURE, "mdssi"); + xmlSignature.sign(xmlSignContext); + + registerIds(document); + + /* + * Completion of undigested ds:References in the ds:Manifests. + */ + for (XMLObject object : objects) { + LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName()); + List 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 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 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 jcaMessageDigest = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo()); + byte[] digestValue = jcaMessageDigest.digest(octets); + + + String description = signatureConfig.getSignatureDescription(); + return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description); + } + + public void postSign(Document document, byte[] signatureValue) + throws IOException, MarshalException, ParserConfigurationException, XmlException { + LOG.log(POILogger.DEBUG, "postSign"); + SignatureInfo.initXmlProvider(); + + /* + * 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(XmlDSigNS, "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, signatureConfig.getSigningCertificateChain()); + } + + registerIds(document); + writeDocument(document); + } + + protected void writeDocument(Document document) throws IOException, XmlException { + XmlOptions xo = new XmlOptions(); + Map namespaceMap = new HashMap(); + for (SignatureFacet sf : signatureConfig.getSignatureFacets()) { + Map sfm = sf.getNamespacePrefixMapping(); + if (sfm != null) { + namespaceMap.putAll(sfm); + } + } + 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 { + // + sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml"); + // + sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs"); + } catch (InvalidFormatException e) { + throw new IOException(e); + } + + String sigContentType = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; + PackagePart sigPart = pkg.getPart(sigPartName); + if (sigPart == null) { + sigPart = pkg.createPart(sigPartName, sigContentType); + } + + OutputStream os = sigPart.getOutputStream(); + SignatureDocument sigDoc = SignatureDocument.Factory.parse(document); + sigDoc.save(os, xo); + os.close(); + + String sigsContentType = "application/vnd.openxmlformats-package.digital-signature-origin"; + PackagePart sigsPart = pkg.getPart(sigsPartName); + if (sigsPart == null) { + // touch empty marker file + sigsPart = pkg.createPart(sigsPartName, sigsContentType); + } + + 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); + } + + /** + * the resulting document needs to be tweaked before it can be digested - + * this applies to the verification and signing step + * + * @param doc + */ + private static void registerIds(Document doc) { + NodeList nl = doc.getElementsByTagNameNS(XmlDSigNS, "Object"); + registerIdAttribute(nl); + nl = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties"); + registerIdAttribute(nl); + } + + public static void registerIdAttribute(NodeList nl) { + for (int i=0; i List safe(List other) { + return other == null ? Collections.EMPTY_LIST : other; + } } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfoConfig.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfoConfig.java index 9468720bf2..e7fc975f3c 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfoConfig.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfoConfig.java @@ -22,8 +22,10 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.UUID; import javax.xml.crypto.URIDereferencer; +import javax.xml.crypto.dsig.CanonicalizationMethod; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.poifs.crypt.HashAlgorithm; @@ -31,8 +33,8 @@ import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.OOXMLSignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet; import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; -import org.apache.poi.poifs.crypt.dsig.facets.SignaturePolicyService; import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet; +import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService; import org.apache.poi.poifs.crypt.dsig.spi.AddressDTO; import org.apache.poi.poifs.crypt.dsig.spi.IdentityDTO; @@ -48,7 +50,21 @@ public class SignatureInfoConfig { private AddressDTO address; private byte[] photo; private SignaturePolicyService signaturePolicyService; - private URIDereferencer uriDereferencer; + private URIDereferencer uriDereferencer; + private String signatureNamespacePrefix; + private String canonicalizationMethod = CanonicalizationMethod.INCLUSIVE; + + /** + * The signature Id attribute value used to create the XML signature. A + * null value will trigger an automatically generated signature Id. + */ + private String packageSignatureId = "idPackageSignature"; + + /** + * Gives back the human-readable description of what the citizen will be + * signing. The default value is "Office OpenXML Document". + */ + private String signatureDescription = "Office OpenXML Document"; public SignatureInfoConfig() { OOXMLURIDereferencer uriDereferencer = new OOXMLURIDereferencer(); @@ -148,8 +164,7 @@ public class SignatureInfoConfig { public SignaturePolicyService getSignaturePolicyService() { return signaturePolicyService; } - public void setSignaturePolicyService( - SignaturePolicyService signaturePolicyService) { + public void setSignaturePolicyService(SignaturePolicyService signaturePolicyService) { this.signaturePolicyService = signaturePolicyService; } public URIDereferencer getUriDereferencer() { @@ -158,6 +173,30 @@ public class SignatureInfoConfig { public void setUriDereferencer(URIDereferencer uriDereferencer) { this.uriDereferencer = uriDereferencer; } - - + public String getSignatureDescription() { + return signatureDescription; + } + public void setSignatureDescription(String signatureDescription) { + this.signatureDescription = signatureDescription; + } + public String getSignatureNamespacePrefix() { + return signatureNamespacePrefix; + } + public void setSignatureNamespacePrefix(String signatureNamespacePrefix) { + this.signatureNamespacePrefix = signatureNamespacePrefix; + } + public String getCanonicalizationMethod() { + return canonicalizationMethod; + } + public void setCanonicalizationMethod(String canonicalizationMethod) { + this.canonicalizationMethod = canonicalizationMethod; + } + public String getPackageSignatureId() { + return packageSignatureId; + } + public void setPackageSignatureId(String packageSignatureId) { + this.packageSignatureId = (packageSignatureId != null) + ? packageSignatureId + : "xmldsig-" + UUID.randomUUID(); + } } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java index 6360d4a7e0..1a6c93987e 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java @@ -15,7 +15,7 @@ import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.spec.TransformParameterSpec; -import org.apache.poi.poifs.crypt.HashAlgorithm; +import org.apache.poi.poifs.crypt.dsig.SignatureInfoConfig; import org.w3c.dom.Document; /** @@ -26,24 +26,10 @@ import org.w3c.dom.Document; */ public class EnvelopedSignatureFacet implements SignatureFacet { - private final HashAlgorithm hashAlgo; + private SignatureInfoConfig signatureConfig; - /** - * Default constructor. Digest algorithm will be SHA-1. - */ - public EnvelopedSignatureFacet() { - this(HashAlgorithm.sha1); - } - - /** - * Main constructor. - * - * @param hashAlgo - * the digest algorithm to be used within the ds:Reference - * element. Possible values: "SHA-1", "SHA-256, or "SHA-512". - */ - public EnvelopedSignatureFacet(HashAlgorithm hashAlgo) { - this.hashAlgo = hashAlgo; + public EnvelopedSignatureFacet(SignatureInfoConfig signatureConfig) { + this.signatureConfig = signatureConfig; } @Override @@ -52,14 +38,12 @@ public class EnvelopedSignatureFacet implements SignatureFacet { } @Override - public void preSign(Document document, - XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, - List references, List objects) - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { - DigestMethod digestMethod = signatureFactory.newDigestMethod( - this.hashAlgo.xmlSignUri, null); + public void preSign(Document document + , XMLSignatureFactory signatureFactory + , List references + , List objects) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestAlgo().xmlSignUri, null); List transforms = new ArrayList(); Transform envelopedTransform = signatureFactory diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java index 3768f58f93..3355cb2bfd 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java @@ -173,12 +173,11 @@ public class KeyInfoSignatureFacet implements SignatureFacet { } @Override - public void preSign(Document document, - XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, - List references, - List objects + public void preSign( + Document document + , XMLSignatureFactory signatureFactory + , List references + , List objects ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { // empty } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java index fbaf0e53f5..8d87cae966 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java @@ -104,20 +104,20 @@ public class OOXMLSignatureFacet implements SignatureFacet { } @Override - public void preSign(Document document, - XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, - List references, List objects) - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException { + public void preSign( + Document document + , XMLSignatureFactory signatureFactory + , List references + , List objects) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException { LOG.log(POILogger.DEBUG, "pre sign"); - addManifestObject(document, signatureFactory, signatureId, references, objects); - addSignatureInfo(document, signatureFactory, signatureId, references, objects); + addManifestObject(document, signatureFactory, references, objects); + addSignatureInfo(document, signatureFactory, references, objects); } private void addManifestObject(Document document, XMLSignatureFactory signatureFactory, - String signatureId, List references, + List references, List objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException { @@ -129,7 +129,7 @@ public class OOXMLSignatureFacet implements SignatureFacet { List objectContent = new ArrayList(); objectContent.add(manifest); - addSignatureTime(document, signatureFactory, signatureId, objectContent); + addSignatureTime(document, signatureFactory, objectContent); XMLObject xo = signatureFactory.newXMLObject(objectContent, objectId, null, null); objects.add(xo); @@ -225,7 +225,6 @@ public class OOXMLSignatureFacet implements SignatureFacet { private void addSignatureTime(Document document, XMLSignatureFactory signatureFactory, - String signatureId, List objectContent) { /* * SignatureTime @@ -247,7 +246,7 @@ public class OOXMLSignatureFacet implements SignatureFacet { List signatureTimeContent = new ArrayList(); signatureTimeContent.add(new DOMStructure(n)); SignatureProperty signatureTimeSignatureProperty = signatureFactory - .newSignatureProperty(signatureTimeContent, "#" + signatureId, + .newSignatureProperty(signatureTimeContent, "#" + signatureConfig.getPackageSignatureId(), "idSignatureTime"); List signaturePropertyContent = new ArrayList(); signaturePropertyContent.add(signatureTimeSignatureProperty); @@ -258,10 +257,10 @@ public class OOXMLSignatureFacet implements SignatureFacet { } private void addSignatureInfo(Document document, - XMLSignatureFactory signatureFactory, - String signatureId, List references, - List objects) throws NoSuchAlgorithmException, - InvalidAlgorithmParameterException { + XMLSignatureFactory signatureFactory, + List references, + List objects) + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { List objectContent = new ArrayList(); SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance(); @@ -273,7 +272,7 @@ public class OOXMLSignatureFacet implements SignatureFacet { List signatureInfoContent = new ArrayList(); signatureInfoContent.add(new DOMStructure(n)); SignatureProperty signatureInfoSignatureProperty = signatureFactory - .newSignatureProperty(signatureInfoContent, "#" + signatureId, + .newSignatureProperty(signatureInfoContent, "#" + signatureConfig.getPackageSignatureId(), "idOfficeV1Details"); List signaturePropertyContent = new ArrayList(); diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java index 420f575bd5..67fe400dba 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java @@ -54,12 +54,11 @@ import org.w3c.dom.NodeList; public class Office2010SignatureFacet implements SignatureFacet { @Override - public void preSign(Document document, - XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, - List references, - List objects + public void preSign( + Document document + , XMLSignatureFactory signatureFactory + , List references + , List objects ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java index eafa3cd461..85b51de049 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java @@ -66,8 +66,6 @@ public interface SignatureFacet { void preSign( Document document , XMLSignatureFactory signatureFactory - , String signatureId - , List signingCertificateChain , List references , List objects ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException; diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java index a93f199e79..0a2f063f67 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java @@ -53,9 +53,11 @@ import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.poifs.crypt.dsig.SignatureInfo; import org.apache.poi.poifs.crypt.dsig.SignatureInfoConfig; -import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService; +import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.xmlbeans.XmlCursor; +import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlString; import org.etsi.uri.x01903.v13.AnyType; import org.etsi.uri.x01903.v13.CertIDListType; @@ -134,8 +136,6 @@ public class XAdESSignatureFacet implements SignatureFacet { @Override public void preSign(Document document, XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, List references, List objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { LOG.log(POILogger.DEBUG, "preSign"); @@ -143,13 +143,13 @@ public class XAdESSignatureFacet implements SignatureFacet { // QualifyingProperties QualifyingPropertiesDocument qualDoc = QualifyingPropertiesDocument.Factory.newInstance(); QualifyingPropertiesType qualifyingProperties = qualDoc.addNewQualifyingProperties(); - qualifyingProperties.setTarget("#" + signatureId); + qualifyingProperties.setTarget("#" + signatureConfig.getPackageSignatureId()); // SignedProperties SignedPropertiesType signedProperties = qualifyingProperties.addNewSignedProperties(); String signedPropertiesId = this.idSignedProperties; if (this.idSignedProperties == null) { - signedPropertiesId = signatureId + "-xades"; + signedPropertiesId = signatureConfig.getPackageSignatureId() + "-xades"; } signedProperties.setId(signedPropertiesId); @@ -164,13 +164,13 @@ public class XAdESSignatureFacet implements SignatureFacet { signedSignatureProperties.setSigningTime(xmlGregorianCalendar); // SigningCertificate - if (null == signingCertificateChain - || signingCertificateChain.isEmpty()) { + if (signatureConfig.getSigningCertificateChain() == null + || signatureConfig.getSigningCertificateChain().isEmpty()) { throw new RuntimeException("no signing certificate chain available"); } CertIDListType signingCertificates = signedSignatureProperties.addNewSigningCertificate(); CertIDType certId = signingCertificates.addNewCert(); - X509Certificate signingCertificate = signingCertificateChain.get(0); + X509Certificate signingCertificate = signatureConfig.getSigningCertificateChain().get(0); setCertID(certId, signingCertificate, this.signatureConfig.getDigestAlgo(), this.issuerNameNoReverseOrder); // ClaimedRole @@ -181,7 +181,7 @@ public class XAdESSignatureFacet implements SignatureFacet { AnyType claimedRole = claimedRolesList.addNewClaimedRole(); XmlString roleString = XmlString.Factory.newInstance(); roleString.setStringValue(this.role); - SignatureInfo.insertXChild(claimedRole, roleString); + insertXChild(claimedRole, roleString); } // XAdES-EPES @@ -208,7 +208,7 @@ public class XAdESSignatureFacet implements SignatureFacet { AnyType sigPolicyQualifier = sigPolicyQualifiers.addNewSigPolicyQualifier(); XmlString spUriElement = XmlString.Factory.newInstance(); spUriElement.setStringValue(signaturePolicyDownloadUrl); - SignatureInfo.insertXChild(sigPolicyQualifier, spUriElement); + insertXChild(sigPolicyQualifier, spUriElement); } } else if (this.signaturePolicyImplied) { SignaturePolicyIdentifierType signaturePolicyIdentifier = @@ -238,7 +238,7 @@ public class XAdESSignatureFacet implements SignatureFacet { // add XAdES ds:Object List xadesObjectContent = new ArrayList(); Element qualDocEl = (Element)document.importNode(qualifyingProperties.getDomNode(), true); - XmlSignatureService.registerIdAttribute(qualDocEl.getElementsByTagName("SignedProperties")); + SignatureInfo.registerIdAttribute(qualDocEl.getElementsByTagName("SignedProperties")); qualDocEl.setAttributeNS(XmlNS, "xmlns:xd", "http://uri.etsi.org/01903/v1.3.2#"); setPrefix(qualDocEl, "http://uri.etsi.org/01903/v1.3.2#", "xd"); xadesObjectContent.add(new DOMStructure(qualDocEl)); @@ -376,4 +376,14 @@ public class XAdESSignatureFacet implements SignatureFacet { return map; } + protected static void insertXChild(XmlObject root, XmlObject child) { + XmlCursor rootCursor = root.newCursor(); + rootCursor.toEndToken(); + XmlCursor childCursor = child.newCursor(); + childCursor.toNextToken(); + childCursor.moveXml(rootCursor); + childCursor.dispose(); + rootCursor.dispose(); + } + } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java index 8d895bac17..9b79afcda9 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java @@ -25,6 +25,7 @@ package org.apache.poi.poifs.crypt.dsig.facets; import static org.apache.poi.poifs.crypt.dsig.SignatureInfo.XmlDSigNS; +import static org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet.insertXChild; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -50,7 +51,6 @@ import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignatureFactory; import org.apache.poi.poifs.crypt.HashAlgorithm; -import org.apache.poi.poifs.crypt.dsig.SignatureInfo; import org.apache.poi.poifs.crypt.dsig.services.RevocationData; import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; @@ -221,7 +221,7 @@ public class XAdESXLSignatureFacet implements SignatureFacet { // xadesv141::TimeStampValidationData if (tsaRevocationDataXadesT.hasRevocationDataEntries()) { ValidationDataType validationData = createValidationData(tsaRevocationDataXadesT); - SignatureInfo.insertXChild(unsignedSigProps, validationData); + insertXChild(unsignedSigProps, validationData); } if (null == this.revocationDataService) { @@ -334,7 +334,7 @@ public class XAdESXLSignatureFacet implements SignatureFacet { this.c14nAlgoId, this.timeStampService); if (tsaRevocationDataXadesX1.hasRevocationDataEntries()) { ValidationDataType timeStampXadesX1ValidationData = createValidationData(tsaRevocationDataXadesX1); - SignatureInfo.insertXChild(unsignedSigProps, timeStampXadesX1ValidationData); + insertXChild(unsignedSigProps, timeStampXadesX1ValidationData); } // marshal XAdES-X @@ -381,8 +381,6 @@ public class XAdESXLSignatureFacet implements SignatureFacet { @Override public void preSign(Document document, XMLSignatureFactory signatureFactory, - String signatureId, - List signingCertificateChain, List references, List objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { // nothing to do here diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java similarity index 95% rename from src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java rename to src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java index d19fb8dfe2..1dbe1b1a15 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java @@ -22,7 +22,7 @@ Copyright (C) 2008-2014 FedICT. ================================================================= */ -package org.apache.poi.poifs.crypt.dsig.facets; +package org.apache.poi.poifs.crypt.dsig.services; /** * Interface for the signature policy service. diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java deleted file mode 100644 index addeff930a..0000000000 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java +++ /dev/null @@ -1,68 +0,0 @@ -/* ==================================================================== - 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.services; - -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.util.List; - -import javax.xml.crypto.MarshalException; -import javax.xml.parsers.ParserConfigurationException; - -import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; -import org.apache.xmlbeans.XmlException; -import org.w3c.dom.Document; - -/** - * Interface for signature service component. - * - * @author Frank Cornelis - * - */ -public interface SignatureService { - - /** - * Pre-sign callback method. Depending on the configuration some parameters - * are passed. The returned value will be signed by the eID Applet. - * - * @param digestInfos - * the optional list of digest infos. - * @return the digest to be signed. - * @throws NoSuchAlgorithmException - */ - DigestInfo preSign(Document document, List digestInfos) - throws NoSuchAlgorithmException; - - /** - * Post-sign callback method. Received the signature value. Depending on the - * configuration the signing certificate chain is also obtained. - * - * @param signatureValue - * @param signingCertificateChain - * the optional chain of signing certificates. - */ - void postSign(Document document, byte[] signatureValue) - throws IOException, MarshalException, ParserConfigurationException, XmlException; -} diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java deleted file mode 100644 index 0501c4775c..0000000000 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java +++ /dev/null @@ -1,481 +0,0 @@ -/* ==================================================================== - 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.services; - -import static org.apache.poi.poifs.crypt.dsig.SignatureInfo.XmlDSigNS; -import static org.apache.poi.poifs.crypt.dsig.SignatureInfo.XmlNS; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.security.InvalidAlgorithmParameterException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.xml.crypto.MarshalException; -import javax.xml.crypto.URIDereferencer; -import javax.xml.crypto.XMLStructure; -import javax.xml.crypto.dsig.CanonicalizationMethod; -import javax.xml.crypto.dsig.DigestMethod; -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.XMLSignatureFactory; -import javax.xml.crypto.dsig.dom.DOMSignContext; -import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactoryConfigurationError; - -import org.apache.jcp.xml.dsig.internal.dom.DOMReference; -import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; -import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.openxml4j.opc.PackageNamespaces; -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.CryptoFunctions; -import org.apache.poi.poifs.crypt.HashAlgorithm; -import org.apache.poi.poifs.crypt.dsig.SignatureInfo; -import org.apache.poi.poifs.crypt.dsig.SignatureInfoConfig; -import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet; -import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; -import org.apache.poi.util.POILogFactory; -import org.apache.poi.util.POILogger; -import org.apache.xml.security.signature.XMLSignature; -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.Event; -import org.w3c.dom.events.EventListener; -import org.w3c.dom.events.EventTarget; -import org.w3c.dom.events.MutationEvent; -import org.xml.sax.SAXException; - - -/** - * Abstract base class for an XML Signature Service implementation. - */ -public class XmlSignatureService implements SignatureService { - private static final POILogger LOG = POILogFactory.getLogger(XmlSignatureService.class); - - protected SignatureInfoConfig signatureConfig; - - private String signatureNamespacePrefix; - private String signatureId = "idPackageSignature"; - - /** - * Main constructor. - */ - public XmlSignatureService(SignatureInfoConfig signatureConfig) { - this.signatureNamespacePrefix = null; - this.signatureConfig = signatureConfig; - } - - public SignatureInfoConfig getSignatureConfig() { - return signatureConfig; - } - - /** - * Sets the signature Id attribute value used to create the XML signature. A - * null value will trigger an automatically generated signature - * Id. - * - * @param signatureId - */ - protected void setSignatureId(String signatureId) { - this.signatureId = signatureId; - } - - /** - * Sets the XML Signature namespace prefix to be used for signature - * creation. A null value will omit the prefixing. - * - * @param signatureNamespacePrefix - */ - protected void setSignatureNamespacePrefix(String signatureNamespacePrefix) { - this.signatureNamespacePrefix = signatureNamespacePrefix; - } - - /** - * Gives back the human-readable description of what the citizen will be - * signing. The default value is "XML Document". Override this method to - * provide the citizen with another description. - * - * @return - */ - protected String getSignatureDescription() { - return "Office OpenXML Document"; - } - - /** - * Gives back the output stream to which to write the signed XML document. - * - * @return - */ - // protected abstract OutputStream getSignedDocumentOutputStream(); - @Override - public DigestInfo preSign(Document document, List digestInfos) - throws NoSuchAlgorithmException { - SignatureInfo.initXmlProvider(); - - LOG.log(POILogger.DEBUG, "preSign"); - HashAlgorithm hashAlgo = this.signatureConfig.getDigestAlgo(); - - byte[] digestValue; - try { - digestValue = getXmlSignatureDigestValue(document, digestInfos); - } catch (Exception e) { - throw new RuntimeException("XML signature error: " + e.getMessage(), e); - } - - String description = getSignatureDescription(); - return new DigestInfo(digestValue, hashAlgo, description); - } - - @Override - public void postSign(Document document, byte[] signatureValue) - throws IOException, MarshalException, ParserConfigurationException, XmlException { - LOG.log(POILogger.DEBUG, "postSign"); - SignatureInfo.initXmlProvider(); - - /* - * Check ds:Signature node. - */ - 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(XmlDSigNS, "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 : this.signatureConfig.getSignatureFacets()) { - signatureFacet.postSign(document, this.signatureConfig.getSigningCertificateChain()); - } - - registerIds(document); - writeDocument(document); - } - - @SuppressWarnings("unchecked") - private byte[] getXmlSignatureDigestValue(Document document, List digestInfos) - throws ParserConfigurationException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException, MarshalException, - javax.xml.crypto.dsig.XMLSignatureException, - TransformerFactoryConfigurationError, TransformerException, - IOException, SAXException, NoSuchProviderException, XmlException, URISyntaxException { - - // 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 ... - final EventTarget et = (EventTarget)document; - EventListener myModificationListener = new EventListener() { - @Override - public void handleEvent(Event e) { - if (e instanceof MutationEvent) { - MutationEvent mutEvt = (MutationEvent)e; - if (mutEvt.getTarget() instanceof Element) { - Element el = (Element)mutEvt.getTarget(); - if ("idPackageObject".equals(el.getAttribute("Id"))) { - et.removeEventListener("DOMSubtreeModified", this, false); - el.setAttributeNS(XmlNS, "xmlns:mdssi", PackageNamespaces.DIGITAL_SIGNATURE); - } - } - } - } - }; - - et.addEventListener("DOMSubtreeModified", myModificationListener, false); - - /* - * Signature context construction. - */ - XMLSignContext xmlSignContext = new DOMSignContext(this.signatureConfig.getKey(), document); - URIDereferencer uriDereferencer = this.signatureConfig.getUriDereferencer(); - if (null != uriDereferencer) { - xmlSignContext.setURIDereferencer(uriDereferencer); - } - - xmlSignContext.putNamespacePrefix( - "http://schemas.openxmlformats.org/package/2006/digital-signature", - "mdssi"); - - if (this.signatureNamespacePrefix != null) { - /* - * OOo doesn't like ds namespaces so per default prefixing is off. - */ - xmlSignContext.putNamespacePrefix(XmlDSigNS, this.signatureNamespacePrefix); - } - - XMLSignatureFactory signatureFactory = SignatureInfo.getSignatureFactory(); - - /* - * Add ds:References that come from signing client local files. - */ - List references = new ArrayList(); - addDigestInfosAsReferences(digestInfos, signatureFactory, references); - - /* - * Invoke the signature facets. - */ - String localSignatureId = this.signatureId; - if (localSignatureId == null) { - localSignatureId = "xmldsig-" + UUID.randomUUID().toString(); - } - List objects = new ArrayList(); - for (SignatureFacet signatureFacet : this.signatureConfig.getSignatureFacets()) { - LOG.log(POILogger.DEBUG, "invoking signature facet: " + signatureFacet.getClass().getSimpleName()); - signatureFacet.preSign(document, signatureFactory, localSignatureId, this.signatureConfig.getSigningCertificateChain(), references, objects); - } - - /* - * ds:SignedInfo - */ - SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(getSignatureMethod(this.signatureConfig.getDigestAlgo()), null); - CanonicalizationMethod canonicalizationMethod = signatureFactory - .newCanonicalizationMethod(getCanonicalizationMethod(), - (C14NMethodParameterSpec) null); - SignedInfo signedInfo = signatureFactory.newSignedInfo( - canonicalizationMethod, signatureMethod, references); - - /* - * JSR105 ds:Signature creation - */ - String signatureValueId = localSignatureId + "-signature-value"; - javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory - .newXMLSignature(signedInfo, null, objects, localSignatureId, - signatureValueId); - - /* - * ds:Signature Marshalling. - */ - xmlSignContext.setDefaultNamespacePrefix(this.signatureNamespacePrefix); - // xmlSignContext.putNamespacePrefix(PackageNamespaces.DIGITAL_SIGNATURE, "mdssi"); - xmlSignature.sign(xmlSignContext); - - registerIds(document); - - /* - * Completion of undigested ds:References in the ds:Manifests. - */ - for (XMLObject object : objects) { - LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName()); - List 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 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 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 jcaMessageDigest = CryptoFunctions.getMessageDigest(this.signatureConfig.getDigestAlgo()); - byte[] digestValue = jcaMessageDigest.digest(octets); - return digestValue; - } - - /** - * the resulting document needs to be tweaked before it can be digested - - * this applies to the verification and signing step - * - * @param doc - */ - public static void registerIds(Document doc) { - NodeList nl = doc.getElementsByTagNameNS(XmlDSigNS, "Object"); - registerIdAttribute(nl); - nl = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties"); - registerIdAttribute(nl); - } - - public static void registerIdAttribute(NodeList nl) { - for (int i=0; i digestInfos, XMLSignatureFactory signatureFactory, List references) - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MalformedURLException { - for (DigestInfo digestInfo : safe(digestInfos)) { - byte[] documentDigestValue = digestInfo.digestValue; - - DigestMethod digestMethod = signatureFactory.newDigestMethod( - digestInfo.hashAlgo.xmlSignUri, null); - - String uri = new File(digestInfo.description).getName(); - - Reference reference = signatureFactory.newReference(uri, - digestMethod, null, null, null, documentDigestValue); - references.add(reference); - } - } - - private String getSignatureMethod(HashAlgorithm hashAlgo) { - if (null == hashAlgo) { - throw new RuntimeException("digest algo is null"); - } - - switch (hashAlgo) { - case sha1: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1; - case sha256: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256; - case sha384: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384; - case sha512: return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512; - case ripemd160: return XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160; - default: break; - } - - throw new RuntimeException("unsupported sign algo: " + hashAlgo); - } - - protected String getCanonicalizationMethod() { - return CanonicalizationMethod.INCLUSIVE; - } - - protected void writeDocument(Document document) throws IOException, XmlException { - XmlOptions xo = new XmlOptions(); - Map namespaceMap = new HashMap(); - for (SignatureFacet sf : this.signatureConfig.getSignatureFacets()) { - Map sfm = sf.getNamespacePrefixMapping(); - if (sfm != null) { - namespaceMap.putAll(sfm); - } - } - 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 = this.signatureConfig.getOpcPackage(); - - PackagePartName sigPartName, sigsPartName; - try { - // - sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml"); - // - sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs"); - } catch (InvalidFormatException e) { - throw new IOException(e); - } - - String sigContentType = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; - PackagePart sigPart = pkg.getPart(sigPartName); - if (sigPart == null) { - sigPart = pkg.createPart(sigPartName, sigContentType); - } - - OutputStream os = sigPart.getOutputStream(); - SignatureDocument sigDoc = SignatureDocument.Factory.parse(document); - sigDoc.save(os, xo); - os.close(); - - String sigsContentType = "application/vnd.openxmlformats-package.digital-signature-origin"; - PackagePart sigsPart = pkg.getPart(sigsPartName); - if (sigsPart == null) { - // touch empty marker file - sigsPart = pkg.createPart(sigsPartName, sigsContentType); - } - - 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); - } - - @SuppressWarnings("unchecked") - public static List safe(List other) { - return other == null ? Collections.EMPTY_LIST : other; - } -} diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java index 4dfc0f46b0..2b9f8b7e74 100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java @@ -67,7 +67,6 @@ import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; import org.apache.poi.poifs.crypt.dsig.services.TSPTimeStampService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator; -import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService; import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo; import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.IOUtils; @@ -120,7 +119,10 @@ public class TestSignatureInfo { for (String testFile : testFiles) { OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); - SignatureInfo si = new SignatureInfo(pkg); + SignatureInfoConfig sic = new SignatureInfoConfig(); + sic.setOpcPackage(pkg); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(sic); List result = si.getSigners(); pkg.revert(); pkg.close(); @@ -146,7 +148,10 @@ public class TestSignatureInfo { for (String testFile : testFiles) { OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); - SignatureInfo si = new SignatureInfo(pkg); + SignatureInfoConfig sic = new SignatureInfoConfig(); + sic.setOpcPackage(pkg); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(sic); List result = si.getSigners(); assertNotNull(result); @@ -164,7 +169,10 @@ public class TestSignatureInfo { public void getMultiSigners() throws Exception { String testFile = "hello-world-signed-twice.docx"; OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ); - SignatureInfo si = new SignatureInfo(pkg); + SignatureInfoConfig sic = new SignatureInfoConfig(); + sic.setOpcPackage(pkg); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(sic); List result = si.getSigners(); assertNotNull(result); @@ -189,12 +197,18 @@ public class TestSignatureInfo { @Test public void testSignSpreadsheetWithSignatureInfo() throws Exception { + initKeyPair("Test", "CN=Test"); String testFile = "hello-world-unsigned.xlsx"; OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE); - SignatureInfo si = new SignatureInfo(pkg); - initKeyPair("Test", "CN=Test"); + SignatureInfoConfig sic = new SignatureInfoConfig(); + sic.setOpcPackage(pkg); + sic.setKey(keyPair.getPrivate()); + sic.setSigningCertificateChain(Collections.singletonList(x509)); + sic.addDefaultFacets(); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(sic); // hash > sha1 doesn't work in excel viewer ... - si.confirmSignature(keyPair.getPrivate(), x509, HashAlgorithm.sha1); + si.confirmSignature(); List signer = si.getSigners(); assertEquals(1, signer.size()); pkg.close(); @@ -223,7 +237,7 @@ public class TestSignatureInfo { certificateChain.add(x509); signatureConfig.setSigningCertificateChain(certificateChain); - signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet()); + signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet(signatureConfig)); signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet(true, false, false)); signatureConfig.addSignatureFacet(new XAdESSignatureFacet(signatureConfig)); @@ -274,12 +288,13 @@ public class TestSignatureInfo { XAdESXLSignatureFacet xadesXLSignatureFacet = new XAdESXLSignatureFacet( timeStampService, revocationDataService); - XmlSignatureService testedInstance = new XmlSignatureService(signatureConfig); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(signatureConfig); Document document = DocumentHelper.createDocument(); // operate - DigestInfo digestInfo = testedInstance.preSign(document, null); + DigestInfo digestInfo = si.preSign(document, null); // verify assertNotNull(digestInfo); @@ -297,10 +312,10 @@ public class TestSignatureInfo { assertNotNull(certDigest.getDigestValue()); // Sign the received XML signature digest value. - byte[] signatureValue = SignatureInfo.signDigest(keyPair.getPrivate(), HashAlgorithm.sha1, digestInfo.digestValue); + byte[] signatureValue = si.signDigest(digestInfo.digestValue); // Operate: postSign - testedInstance.postSign(document, signatureValue); + si.postSign(document, signatureValue); DOMValidateContext domValidateContext = new DOMValidateContext( KeySelector.singletonKeySelector(keyPair.getPublic()), @@ -341,12 +356,13 @@ public class TestSignatureInfo { signatureConfig.setOpcPackage(pkgCopy); signatureConfig.addDefaultFacets(); - XmlSignatureService signatureService = new XmlSignatureService(signatureConfig); + SignatureInfo si = new SignatureInfo(); + si.setSignatureConfig(signatureConfig); Document document = DocumentHelper.createDocument(); // operate - DigestInfo digestInfo = signatureService.preSign(document, null); + DigestInfo digestInfo = si.preSign(document, null); // verify assertNotNull(digestInfo); @@ -357,13 +373,13 @@ public class TestSignatureInfo { assertNotNull(digestInfo.digestValue); // setup: key material, signature value - byte[] signatureValue = SignatureInfo.signDigest(keyPair.getPrivate(), HashAlgorithm.sha1, digestInfo.digestValue); + byte[] signatureValue = si.signDigest(digestInfo.digestValue); // operate: postSign - signatureService.postSign(document, signatureValue); + si.postSign(document, signatureValue); // verify: signature - SignatureInfo si = new SignatureInfo(pkgCopy); + si.getSignatureConfig().setOpcPackage(pkgCopy); List signers = si.getSigners(); assertEquals(signerCount, signers.size()); -- 2.39.5