]> source.dussan.org Git - poi.git/blob
f76d69d6a8789abe0d9c0d70e1cc130ff13e9411
[poi.git] /
1
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
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
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 ==================================================================== */
18
19
20 /*
21  * Based on the eID Applet Project code.
22  * Original Copyright (C) 2008-2009 FedICT.
23  */
24
25 package org.apache.poi.ooxml.signature.service.signer.ooxml;
26
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.net.URL;
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;
40
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;
57
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;
71
72
73
74 /**
75  * Signature Service implementation for Office OpenXML document format XML
76  * signatures.
77  */
78 public abstract class AbstractOOXMLSignatureService extends AbstractXmlSignatureService {
79
80     static final Log LOG = LogFactory.getLog(AbstractOOXMLSignatureService.class);
81
82     protected AbstractOOXMLSignatureService() {
83         addSignatureAspect(new OOXMLSignatureAspect(this));
84     }
85
86     @Override
87     protected String getSignatureDescription() {
88         return "Office OpenXML Document";
89     }
90
91     public String getFilesDigestAlgorithm() {
92         return null;
93     }
94
95     @Override
96     protected final URIDereferencer getURIDereferencer() {
97         URL ooxmlUrl = getOfficeOpenXMLDocumentURL();
98         return new OOXMLURIDereferencer(ooxmlUrl);
99     }
100
101     @Override
102     protected String getCanonicalizationMethod() {
103         return CanonicalizationMethod.INCLUSIVE;
104     }
105
106     @Override
107     protected void postSign(Element signatureElement, List<X509Certificate> signingCertificateChain) {
108         // TODO: implement as SignatureAspect
109         LOG.debug("postSign: adding ds:KeyInfo");
110         /*
111          * Make sure we insert right after the ds:SignatureValue element.
112          */
113         Node nextSibling;
114         NodeList objectNodeList = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object");
115         if (0 == objectNodeList.getLength()) {
116             nextSibling = null;
117         } else {
118             nextSibling = objectNodeList.item(0);
119         }
120         /*
121          * Add a ds:KeyInfo entry.
122          */
123         KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance();
124         List<Object> x509DataObjects = new LinkedList<Object>();
125
126         X509Certificate signingCertificate = signingCertificateChain.get(0);
127         KeyValue keyValue;
128         try {
129             keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey());
130         } catch (KeyException e) {
131             throw new RuntimeException("key exception: " + e.getMessage(), e);
132         }
133
134         for (X509Certificate certificate : signingCertificateChain) {
135             x509DataObjects.add(certificate);
136         }
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;
145
146             public String getAlgorithm() {
147                 return null;
148             }
149
150             public byte[] getEncoded() {
151                 return null;
152             }
153
154             public String getFormat() {
155                 return null;
156             }
157         };
158         XMLSignContext xmlSignContext = new DOMSignContext(key, signatureElement);
159         DOMCryptoContext domCryptoContext = (DOMCryptoContext) xmlSignContext;
160         String dsPrefix = null;
161         // String dsPrefix = "ds";
162         try {
163             domKeyInfo.marshal(signatureElement, nextSibling, dsPrefix, domCryptoContext);
164         } catch (MarshalException e) {
165             throw new RuntimeException("marshall error: " + e.getMessage(), e);
166         }
167     }
168
169     private class OOXMLSignedDocumentOutputStream extends ByteArrayOutputStream {
170
171         @Override
172         public void close() throws IOException {
173             LOG.debug("close OOXML signed document output stream");
174             super.close();
175             try {
176                 outputSignedOfficeOpenXMLDocument(this.toByteArray());
177             } catch (Exception e) {
178                 throw new IOException("generic error: " + e.getMessage(), e);
179             }
180         }
181     }
182
183     /**
184      * The output stream to which to write the signed Office OpenXML file.
185      * 
186      * @return
187      */
188     abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream();
189
190     /**
191      * Gives back the URL of the OOXML to be signed.
192      * 
193      * @return
194      */
195     abstract protected URL getOfficeOpenXMLDocumentURL();
196
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");
202         }
203
204         String signatureZipEntryName = "_xmlsignatures/sig-" + UUID.randomUUID().toString() + ".xml";
205         LOG.debug("signature ZIP entry name: " + signatureZipEntryName);
206         /*
207          * Copy the original OOXML content to the signed OOXML package. During
208          * copying some files need to changed.
209          */
210         ZipOutputStream zipOutputStream = copyOOXMLContent(signatureZipEntryName, signedOOXMLOutputStream);
211
212         /*
213          * Add the OOXML XML signature file to the OOXML package.
214          */
215         ZipEntry zipEntry = new ZipEntry(signatureZipEntryName);
216         zipOutputStream.putNextEntry(zipEntry);
217         IOUtils.write(signatureData, zipOutputStream);
218         zipOutputStream.close();
219     }
220
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());
226         ZipEntry zipEntry;
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();
235
236                 /*
237                  * We need to add an Override element.
238                  */
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);
243
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()) {
248                     /*
249                      * Add Default element for 'sigs' extension.
250                      */
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);
255                 }
256
257                 writeDocumentNoClosing(contentTypesDocument, zipOutputStream, false);
258             } else if ("_rels/.rels".equals(zipEntry.getName())) {
259                 Document relsDocument = loadDocumentNoClose(zipInputStream);
260
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']",
264                                                 nsElement);
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");
270
271                     relsDocument.getDocumentElement().appendChild(relationshipElement);
272                 }
273
274                 writeDocumentNoClosing(relsDocument, zipOutputStream, false);
275             } else if ("_xmlsignatures/_rels/origin.sigs.rels".equals(zipEntry.getName())) {
276                 hasOriginSigsRels = true;
277                 Document originSignRelsDocument = loadDocumentNoClose(zipInputStream);
278
279                 Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships",
280                                                 "Relationship");
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);
288
289                 writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
290             } else {
291                 IOUtils.copy(zipInputStream, zipOutputStream);
292             }
293         }
294
295         if (false == hasOriginSigsRels) {
296             /*
297              * Add signature relationships document.
298              */
299             addOriginSigsRels(signatureZipEntryName, zipOutputStream);
300             addOriginSigs(zipOutputStream);
301         }
302
303         /*
304          * Return.
305          */
306         zipInputStream.close();
307         return zipOutputStream;
308     }
309
310     private void addOriginSigs(ZipOutputStream zipOutputStream) throws IOException {
311         zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/origin.sigs"));
312     }
313
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();
320
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);
324
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);
333
334         zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/_rels/origin.sigs.rels"));
335         writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false);
336     }
337
338     @Override
339     protected OutputStream getSignedDocumentOutputStream() {
340         LOG.debug("get signed document output stream");
341         /*
342          * Create each time a new object; we want an empty output stream to
343          * start with.
344          */
345         OutputStream signedDocumentOutputStream = new OOXMLSignedDocumentOutputStream();
346         return signedDocumentOutputStream;
347     }
348 }