2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
21 * Based on the eID Applet Project code.
22 * Original Copyright (C) 2008-2009 FedICT.
25 package org.apache.poi.ooxml.signature.service.signer.ooxml;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.OutputStream;
31 import java.security.Key;
32 import java.security.KeyException;
33 import java.security.cert.X509Certificate;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.UUID;
37 import java.util.zip.ZipEntry;
38 import java.util.zip.ZipInputStream;
39 import java.util.zip.ZipOutputStream;
41 import javax.xml.crypto.MarshalException;
42 import javax.xml.crypto.URIDereferencer;
43 import javax.xml.crypto.dom.DOMCryptoContext;
44 import javax.xml.crypto.dsig.CanonicalizationMethod;
45 import javax.xml.crypto.dsig.XMLSignContext;
46 import javax.xml.crypto.dsig.dom.DOMSignContext;
47 import javax.xml.crypto.dsig.keyinfo.KeyInfo;
48 import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
49 import javax.xml.crypto.dsig.keyinfo.KeyValue;
50 import javax.xml.crypto.dsig.keyinfo.X509Data;
51 import javax.xml.parsers.DocumentBuilder;
52 import javax.xml.parsers.DocumentBuilderFactory;
53 import javax.xml.parsers.ParserConfigurationException;
54 import javax.xml.transform.TransformerConfigurationException;
55 import javax.xml.transform.TransformerException;
56 import javax.xml.transform.TransformerFactoryConfigurationError;
58 import org.apache.commons.io.FilenameUtils;
59 import org.apache.commons.io.IOUtils;
60 import org.apache.commons.logging.Log;
61 import org.apache.commons.logging.LogFactory;
62 import org.apache.poi.ooxml.signature.service.signer.AbstractXmlSignatureService;
63 import org.apache.xml.security.utils.Constants;
64 import org.apache.xpath.XPathAPI;
65 import org.jcp.xml.dsig.internal.dom.DOMKeyInfo;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.Element;
68 import org.w3c.dom.Node;
69 import org.w3c.dom.NodeList;
70 import org.xml.sax.SAXException;
75 * Signature Service implementation for Office OpenXML document format XML
78 public abstract class AbstractOOXMLSignatureService extends AbstractXmlSignatureService {
80 static final Log LOG = LogFactory.getLog(AbstractOOXMLSignatureService.class);
82 protected AbstractOOXMLSignatureService() {
83 addSignatureAspect(new OOXMLSignatureAspect(this));
87 protected String getSignatureDescription() {
88 return "Office OpenXML Document";
91 public String getFilesDigestAlgorithm() {
96 protected final URIDereferencer getURIDereferencer() {
97 URL ooxmlUrl = getOfficeOpenXMLDocumentURL();
98 return new OOXMLURIDereferencer(ooxmlUrl);
102 protected String getCanonicalizationMethod() {
103 return CanonicalizationMethod.INCLUSIVE;
107 protected void postSign(Element signatureElement, List<X509Certificate> signingCertificateChain) {
108 // TODO: implement as SignatureAspect
109 LOG.debug("postSign: adding ds:KeyInfo");
111 * Make sure we insert right after the ds:SignatureValue element.
114 NodeList objectNodeList = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object");
115 if (0 == objectNodeList.getLength()) {
118 nextSibling = objectNodeList.item(0);
121 * Add a ds:KeyInfo entry.
123 KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance();
124 List<Object> x509DataObjects = new LinkedList<Object>();
126 X509Certificate signingCertificate = signingCertificateChain.get(0);
129 keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey());
130 } catch (KeyException e) {
131 throw new RuntimeException("key exception: " + e.getMessage(), e);
134 for (X509Certificate certificate : signingCertificateChain) {
135 x509DataObjects.add(certificate);
137 X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects);
138 List<Object> keyInfoContent = new LinkedList<Object>();
139 keyInfoContent.add(keyValue);
140 keyInfoContent.add(x509Data);
141 KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoContent);
142 DOMKeyInfo domKeyInfo = (DOMKeyInfo) keyInfo;
143 Key key = new Key() {
144 private static final long serialVersionUID = 1L;
146 public String getAlgorithm() {
150 public byte[] getEncoded() {
154 public String getFormat() {
158 XMLSignContext xmlSignContext = new DOMSignContext(key, signatureElement);
159 DOMCryptoContext domCryptoContext = (DOMCryptoContext) xmlSignContext;
160 String dsPrefix = null;
161 // String dsPrefix = "ds";
163 domKeyInfo.marshal(signatureElement, nextSibling, dsPrefix, domCryptoContext);
164 } catch (MarshalException e) {
165 throw new RuntimeException("marshall error: " + e.getMessage(), e);
169 private class OOXMLSignedDocumentOutputStream extends ByteArrayOutputStream {
172 public void close() throws IOException {
173 LOG.debug("close OOXML signed document output stream");
176 outputSignedOfficeOpenXMLDocument(this.toByteArray());
177 } catch (Exception e) {
178 throw new IOException("generic error: " + e.getMessage(), e);
184 * The output stream to which to write the signed Office OpenXML file.
188 abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream();
191 * Gives back the URL of the OOXML to be signed.
195 abstract protected URL getOfficeOpenXMLDocumentURL();
197 private void outputSignedOfficeOpenXMLDocument(byte[] signatureData) throws IOException, ParserConfigurationException, SAXException, TransformerException {
198 LOG.debug("output signed Office OpenXML document");
199 OutputStream signedOOXMLOutputStream = getSignedOfficeOpenXMLDocumentOutputStream();
200 if (null == signedOOXMLOutputStream) {
201 throw new NullPointerException("signedOOXMLOutputStream is null");
204 String signatureZipEntryName = "_xmlsignatures/sig-" + UUID.randomUUID().toString() + ".xml";
205 LOG.debug("signature ZIP entry name: " + signatureZipEntryName);
207 * Copy the original OOXML content to the signed OOXML package. During
208 * copying some files need to changed.
210 ZipOutputStream zipOutputStream = copyOOXMLContent(signatureZipEntryName, signedOOXMLOutputStream);
213 * Add the OOXML XML signature file to the OOXML package.
215 ZipEntry zipEntry = new ZipEntry(signatureZipEntryName);
216 zipOutputStream.putNextEntry(zipEntry);
217 IOUtils.write(signatureData, zipOutputStream);
218 zipOutputStream.close();
221 private ZipOutputStream copyOOXMLContent(String signatureZipEntryName, OutputStream signedOOXMLOutputStream) throws IOException,
222 ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerFactoryConfigurationError,
223 TransformerException {
224 ZipOutputStream zipOutputStream = new ZipOutputStream(signedOOXMLOutputStream);
225 ZipInputStream zipInputStream = new ZipInputStream(this.getOfficeOpenXMLDocumentURL().openStream());
227 boolean hasOriginSigsRels = false;
228 while (null != (zipEntry = zipInputStream.getNextEntry())) {
229 LOG.debug("copy ZIP entry: " + zipEntry.getName());
230 ZipEntry newZipEntry = new ZipEntry(zipEntry.getName());
231 zipOutputStream.putNextEntry(newZipEntry);
232 if ("[Content_Types].xml".equals(zipEntry.getName())) {
233 Document contentTypesDocument = loadDocumentNoClose(zipInputStream);
234 Element typesElement = contentTypesDocument.getDocumentElement();
237 * We need to add an Override element.
239 Element overrideElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Override");
240 overrideElement.setAttribute("PartName", "/" + signatureZipEntryName);
241 overrideElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml");
242 typesElement.appendChild(overrideElement);
244 Element nsElement = contentTypesDocument.createElement("ns");
245 nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types");
246 NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Default[@Extension='sigs']", nsElement);
247 if (0 == nodeList.getLength()) {
249 * Add Default element for 'sigs' extension.
251 Element defaultElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Default");
252 defaultElement.setAttribute("Extension", "sigs");
253 defaultElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-origin");
254 typesElement.appendChild(defaultElement);
257 writeDocumentNoClosing(contentTypesDocument, zipOutputStream, false);
258 } else if ("_rels/.rels".equals(zipEntry.getName())) {
259 Document relsDocument = loadDocumentNoClose(zipInputStream);
261 Element nsElement = relsDocument.createElement("ns");
262 nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships");
263 NodeList nodeList = XPathAPI.selectNodeList(relsDocument, "/tns:Relationships/tns:Relationship[@Target='_xmlsignatures/origin.sigs']",
265 if (0 == nodeList.getLength()) {
266 Element relationshipElement = relsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship");
267 relationshipElement.setAttribute("Id", "rel-id-" + UUID.randomUUID().toString());
268 relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin");
269 relationshipElement.setAttribute("Target", "_xmlsignatures/origin.sigs");
271 relsDocument.getDocumentElement().appendChild(relationshipElement);
274 writeDocumentNoClosing(relsDocument, zipOutputStream, false);
275 } else if ("_xmlsignatures/_rels/origin.sigs.rels".equals(zipEntry.getName())) {
276 hasOriginSigsRels = true;
277 Document originSignRelsDocument = loadDocumentNoClose(zipInputStream);
279 Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships",
281 String relationshipId = "rel-" + UUID.randomUUID().toString();
282 relationshipElement.setAttribute("Id", relationshipId);
283 relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature");
284 String target = FilenameUtils.getName(signatureZipEntryName);
285 LOG.debug("target: " + target);
286 relationshipElement.setAttribute("Target", target);
287 originSignRelsDocument.getDocumentElement().appendChild(relationshipElement);
289 writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
291 IOUtils.copy(zipInputStream, zipOutputStream);
295 if (false == hasOriginSigsRels) {
297 * Add signature relationships document.
299 addOriginSigsRels(signatureZipEntryName, zipOutputStream);
300 addOriginSigs(zipOutputStream);
306 zipInputStream.close();
307 return zipOutputStream;
310 private void addOriginSigs(ZipOutputStream zipOutputStream) throws IOException {
311 zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/origin.sigs"));
314 private void addOriginSigsRels(String signatureZipEntryName, ZipOutputStream zipOutputStream) throws ParserConfigurationException, IOException,
315 TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
316 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
317 documentBuilderFactory.setNamespaceAware(true);
318 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
319 Document originSignRelsDocument = documentBuilder.newDocument();
321 Element relationshipsElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationships");
322 relationshipsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.openxmlformats.org/package/2006/relationships");
323 originSignRelsDocument.appendChild(relationshipsElement);
325 Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship");
326 String relationshipId = "rel-" + UUID.randomUUID().toString();
327 relationshipElement.setAttribute("Id", relationshipId);
328 relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature");
329 String target = FilenameUtils.getName(signatureZipEntryName);
330 LOG.debug("target: " + target);
331 relationshipElement.setAttribute("Target", target);
332 relationshipsElement.appendChild(relationshipElement);
334 zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/_rels/origin.sigs.rels"));
335 writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
339 protected OutputStream getSignedDocumentOutputStream() {
340 LOG.debug("get signed document output stream");
342 * Create each time a new object; we want an empty output stream to
345 OutputStream signedDocumentOutputStream = new OOXMLSignedDocumentOutputStream();
346 return signedDocumentOutputStream;