/* * eID Applet Project. * Copyright (C) 2008-2009 FedICT. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ /* * Copyright (C) 2008-2009 FedICT. * This file is part of the eID Applet Project. * * Licensed 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. */ package be.fedict.eid.applet.service.signer.ooxml; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.xml.crypto.URIDereferencer; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactoryConfigurationError; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.security.utils.Constants; import org.apache.xpath.XPathAPI; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import be.fedict.eid.applet.service.signer.AbstractXmlSignatureService; import be.fedict.eid.applet.service.signer.DigestAlgo; import be.fedict.eid.applet.service.signer.facets.KeyInfoSignatureFacet; import be.fedict.eid.applet.service.signer.facets.XAdESSignatureFacet; import be.fedict.eid.applet.service.signer.time.ConstantLocalClock; /** * Signature Service implementation for Office OpenXML document format XML * signatures. * * @author Frank Cornelis * */ public abstract class AbstractOOXMLSignatureService extends AbstractXmlSignatureService { static final Log LOG = LogFactory.getLog(AbstractOOXMLSignatureService.class); private final XAdESSignatureFacet xadesSignatureFacet; protected AbstractOOXMLSignatureService(DigestAlgo digestAlgo) { super(digestAlgo); ConstantLocalClock clock = new ConstantLocalClock(); addSignatureFacet(new OOXMLSignatureFacet(this, clock, digestAlgo)); addSignatureFacet(new KeyInfoSignatureFacet(true, false, false)); this.xadesSignatureFacet = new XAdESSignatureFacet(clock, digestAlgo); this.xadesSignatureFacet.setXadesNamespacePrefix("xd"); this.xadesSignatureFacet.setIdSignedProperties("idSignedProperties"); this.xadesSignatureFacet.setSignaturePolicyImplied(true); /* * Work-around for Office 2010. */ this.xadesSignatureFacet.setIssuerNameNoReverseOrder(true); setSignatureId("idPackageSignature"); addSignatureFacet(this.xadesSignatureFacet); addSignatureFacet(new Office2010SignatureFacet()); } /** * Gives back the used XAdES signature facet. * * @return */ protected XAdESSignatureFacet getXAdESSignatureFacet() { return this.xadesSignatureFacet; } @Override protected String getSignatureDescription() { return "Office OpenXML Document"; } public String getFilesDigestAlgorithm() { return null; } @Override protected final URIDereferencer getURIDereferencer() { URL ooxmlUrl = getOfficeOpenXMLDocumentURL(); return new OOXMLURIDereferencer(ooxmlUrl); } @Override protected String getCanonicalizationMethod() { return CanonicalizationMethod.INCLUSIVE; } private class OOXMLSignedDocumentOutputStream extends ByteArrayOutputStream { @Override public void close() throws IOException { LOG.debug("close OOXML signed document output stream"); super.close(); try { outputSignedOfficeOpenXMLDocument(this.toByteArray()); } catch (Exception e) { throw new IOException("generic error: " + e.getMessage(), e); } } } /** * The output stream to which to write the signed Office OpenXML file. * * @return */ abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream(); /** * Gives back the URL of the OOXML to be signed. * * @return */ abstract protected URL getOfficeOpenXMLDocumentURL(); private void outputSignedOfficeOpenXMLDocument(byte[] signatureData) throws IOException, ParserConfigurationException, SAXException, TransformerException { LOG.debug("output signed Office OpenXML document"); OutputStream signedOOXMLOutputStream = getSignedOfficeOpenXMLDocumentOutputStream(); if (null == signedOOXMLOutputStream) { throw new NullPointerException("signedOOXMLOutputStream is null"); } String signatureZipEntryName = "_xmlsignatures/sig-" + UUID.randomUUID().toString() + ".xml"; LOG.debug("signature ZIP entry name: " + signatureZipEntryName); /* * Copy the original OOXML content to the signed OOXML package. During * copying some files need to changed. */ ZipOutputStream zipOutputStream = copyOOXMLContent(signatureZipEntryName, signedOOXMLOutputStream); /* * Add the OOXML XML signature file to the OOXML package. */ ZipEntry zipEntry = new ZipEntry(signatureZipEntryName); zipOutputStream.putNextEntry(zipEntry); IOUtils.write(signatureData, zipOutputStream); zipOutputStream.close(); } private ZipOutputStream copyOOXMLContent(String signatureZipEntryName, OutputStream signedOOXMLOutputStream) throws IOException, ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException { ZipOutputStream zipOutputStream = new ZipOutputStream(signedOOXMLOutputStream); ZipInputStream zipInputStream = new ZipInputStream(this.getOfficeOpenXMLDocumentURL().openStream()); ZipEntry zipEntry; boolean hasOriginSigsRels = false; while (null != (zipEntry = zipInputStream.getNextEntry())) { LOG.debug("copy ZIP entry: " + zipEntry.getName()); ZipEntry newZipEntry = new ZipEntry(zipEntry.getName()); zipOutputStream.putNextEntry(newZipEntry); if ("[Content_Types].xml".equals(zipEntry.getName())) { Document contentTypesDocument = loadDocumentNoClose(zipInputStream); Element typesElement = contentTypesDocument.getDocumentElement(); /* * We need to add an Override element. */ Element overrideElement = contentTypesDocument .createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Override"); overrideElement.setAttribute("PartName", "/" + signatureZipEntryName); overrideElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); typesElement.appendChild(overrideElement); Element nsElement = contentTypesDocument.createElement("ns"); nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types"); NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Default[@Extension='sigs']", nsElement); if (0 == nodeList.getLength()) { /* * Add Default element for 'sigs' extension. */ Element defaultElement = contentTypesDocument .createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Default"); defaultElement.setAttribute("Extension", "sigs"); defaultElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-origin"); typesElement.appendChild(defaultElement); } writeDocumentNoClosing(contentTypesDocument, zipOutputStream, false); } else if ("_rels/.rels".equals(zipEntry.getName())) { Document relsDocument = loadDocumentNoClose(zipInputStream); Element nsElement = relsDocument.createElement("ns"); nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships"); NodeList nodeList = XPathAPI.selectNodeList(relsDocument, "/tns:Relationships/tns:Relationship[@Target='_xmlsignatures/origin.sigs']", nsElement); if (0 == nodeList.getLength()) { Element relationshipElement = relsDocument.createElementNS( "http://schemas.openxmlformats.org/package/2006/relationships", "Relationship"); relationshipElement.setAttribute("Id", "rel-id-" + UUID.randomUUID().toString()); relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"); relationshipElement.setAttribute("Target", "_xmlsignatures/origin.sigs"); relsDocument.getDocumentElement().appendChild(relationshipElement); } writeDocumentNoClosing(relsDocument, zipOutputStream, false); } else if ("_xmlsignatures/_rels/origin.sigs.rels".equals(zipEntry.getName())) { hasOriginSigsRels = true; Document originSignRelsDocument = loadDocumentNoClose(zipInputStream); Element relationshipElement = originSignRelsDocument.createElementNS( "http://schemas.openxmlformats.org/package/2006/relationships", "Relationship"); String relationshipId = "rel-" + UUID.randomUUID().toString(); relationshipElement.setAttribute("Id", relationshipId); relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"); String target = FilenameUtils.getName(signatureZipEntryName); LOG.debug("target: " + target); relationshipElement.setAttribute("Target", target); originSignRelsDocument.getDocumentElement().appendChild(relationshipElement); writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false); } else { IOUtils.copy(zipInputStream, zipOutputStream); } } if (false == hasOriginSigsRels) { /* * Add signature relationships document. */ addOriginSigsRels(signatureZipEntryName, zipOutputStream); addOriginSigs(zipOutputStream); } /* * Return. */ zipInputStream.close(); return zipOutputStream; } private void addOriginSigs(ZipOutputStream zipOutputStream) throws IOException { zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/origin.sigs")); } private void addOriginSigsRels(String signatureZipEntryName, ZipOutputStream zipOutputStream) throws ParserConfigurationException, IOException, TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document originSignRelsDocument = documentBuilder.newDocument(); Element relationshipsElement = originSignRelsDocument .createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationships"); relationshipsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); originSignRelsDocument.appendChild(relationshipsElement); Element relationshipElement = originSignRelsDocument .createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship"); String relationshipId = "rel-" + UUID.randomUUID().toString(); relationshipElement.setAttribute("Id", relationshipId); relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"); String target = FilenameUtils.getName(signatureZipEntryName); LOG.debug("target: " + target); relationshipElement.setAttribute("Target", target); relationshipsElement.appendChild(relationshipElement); zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/_rels/origin.sigs.rels")); writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false); } @Override protected OutputStream getSignedDocumentOutputStream() { LOG.debug("get signed document output stream"); /* * Create each time a new object; we want an empty output stream to * start with. */ OutputStream signedDocumentOutputStream = new OOXMLSignedDocumentOutputStream(); return signedDocumentOutputStream; } }