1 package org.eparapher.core.signature;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.OutputStream;
9 import java.io.StringReader;
10 import java.security.InvalidAlgorithmParameterException;
11 import java.security.NoSuchAlgorithmException;
12 import java.security.cert.X509Certificate;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16
17 import javax.xml.crypto.MarshalException;
18 import javax.xml.crypto.XMLStructure;
19 import javax.xml.crypto.dom.DOMStructure;
20 import javax.xml.crypto.dsig.CanonicalizationMethod;
21 import javax.xml.crypto.dsig.DigestMethod;
22 import javax.xml.crypto.dsig.Reference;
23 import javax.xml.crypto.dsig.SignatureMethod;
24 import javax.xml.crypto.dsig.SignedInfo;
25 import javax.xml.crypto.dsig.Transform;
26 import javax.xml.crypto.dsig.XMLObject;
27 import javax.xml.crypto.dsig.XMLSignature;
28 import javax.xml.crypto.dsig.XMLSignatureException;
29 import javax.xml.crypto.dsig.XMLSignatureFactory;
30 import javax.xml.crypto.dsig.dom.DOMSignContext;
31 import javax.xml.crypto.dsig.keyinfo.KeyInfo;
32 import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
33 import javax.xml.crypto.dsig.keyinfo.X509Data;
34 import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
35 import javax.xml.crypto.dsig.spec.TransformParameterSpec;
36 import javax.xml.crypto.dsig.spec.XPathFilter2ParameterSpec;
37 import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
38 import javax.xml.crypto.dsig.spec.XPathType;
39 import javax.xml.crypto.dsig.spec.XSLTTransformParameterSpec;
40 import javax.xml.parsers.DocumentBuilder;
41 import javax.xml.parsers.DocumentBuilderFactory;
42 import javax.xml.parsers.ParserConfigurationException;
43 import javax.xml.transform.OutputKeys;
44 import javax.xml.transform.Transformer;
45 import javax.xml.transform.TransformerException;
46 import javax.xml.transform.TransformerFactory;
47 import javax.xml.transform.dom.DOMSource;
48 import javax.xml.transform.stream.StreamResult;
49
50 import org.apache.log4j.Logger;
51 import org.eparapher.core.EParapherManager;
52 import org.eparapher.core.crypto.EPKeystoreManager;
53 import org.eparapher.core.crypto.keystore.IUserKeystore;
54
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.xml.sax.InputSource;
58 import org.xml.sax.SAXException;
59 import org.xml.sax.EntityResolver;
60
61 public class XMLSigner {
62
63 private static Logger log = Logger.getLogger(XMLSigner.class);
64
65 private IUserKeystore userpkandcert;
66
67 private Document document;
68
69 public XMLSigner() {
70
71 }
72
73
74
75
76
77
78
79 public String sign( String xmlSourceFile, XMLSignatureParameters xmlsignparams ) {
80
81 userpkandcert = EPKeystoreManager.getInstance().getUserkeystore();
82
83 if ( loadXmlSourceFile(xmlSourceFile,xmlsignparams) )
84 if ( signXMLDoc(xmlsignparams) )
85 return saveSignedXmlFile(xmlSourceFile, xmlsignparams);
86 return null;
87 }
88
89
90
91
92 private String saveSignedXmlFile( String xmlSourceFile, XMLSignatureParameters xmlsignparams ) {
93 String xmlDestFile;
94
95
96 if ( EParapherManager.getInstance().getSettings().isXMLSignatureReplaceFile() ) {
97 File original = new File(xmlSourceFile);
98
99 original.delete();
100 xmlDestFile = xmlSourceFile ;
101 } else {
102 xmlDestFile= xmlSourceFile + ".signed.xml";
103 }
104
105
106 log.info("Saving Signed XML document to " + xmlDestFile);
107 OutputStream os = null;
108 try {
109 os = new FileOutputStream(xmlDestFile);
110 } catch (FileNotFoundException e) {
111 String msg = " Cannot write in file : " + xmlDestFile;
112 EParapherManager.getInstance().getUI().errorMessage(msg,e);
113 log.error(msg, e);
114
115 log.error(" Output signed xml to console : ");
116 os = System.out;
117 }
118
119
120 TransformerFactory tf = TransformerFactory.newInstance();
121 Transformer trans;
122 try {
123 trans = tf.newTransformer();
124 trans.setOutputProperty(OutputKeys.INDENT, "yes");
125 trans.transform(new DOMSource(document), new StreamResult(os));
126 } catch (TransformerException e) {
127 log.error(""+e.getLocalizedMessage(),e);
128 }
129 if (os!=null){
130 try {
131 os.flush();
132 os.close();
133 return xmlDestFile;
134 } catch (IOException e) {
135 log.error(""+e.getLocalizedMessage(),e);
136 }
137 }
138 return null;
139 }
140
141 private boolean loadXmlSourceFile(String xmlSourceFile, XMLSignatureParameters xmlsignparams) {
142
143
144 log.info("Loading XML document to sign : " + xmlSourceFile);
145 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
146 dbf.setNamespaceAware(false);
147 dbf.setValidating(false);
148 try {
149 DocumentBuilder db = dbf.newDocumentBuilder();
150
151
152 if (!xmlsignparams.isDTDValidation())
153 db.setEntityResolver( new DTDVerificationDisabled() );
154
155 document = db.parse(new FileInputStream(xmlSourceFile));
156
157 return true;
158 } catch (FileNotFoundException e) {
159 log.error(""+e.getLocalizedMessage(),e);
160 } catch (SAXException e) {
161 log.error(""+e.getLocalizedMessage(),e);
162 } catch (IOException e) {
163 log.error(""+e.getLocalizedMessage(),e);
164 } catch (ParserConfigurationException e) {
165 log.error(""+e.getLocalizedMessage(),e);
166 }
167 return false;
168 }
169
170 private boolean signXMLDoc(XMLSignatureParameters xmlsignparams) {
171
172
173
174 log.debug("Preparing XML Signature");
175 XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
176
177
178
179 Reference ref = null;
180 XMLObject obj = null;
181 try {
182 DigestMethod digest = fac.newDigestMethod(xmlsignparams.getDigestAlg(), null);
183 if (xmlsignparams.isEnveloped()) {
184
185 List listTransform = Collections.singletonList(fac.newTransform(xmlsignparams.getTransform(), getTransformParameterSpec(xmlsignparams.getTransform()) ));
186
187 String reference="";
188 ref = fac.newReference(reference, digest, listTransform, null, null);
189 } else if (xmlsignparams.isEnveloping()) {
190
191
192 String refe = "#"+xmlsignparams.getObjectIds();
193 ref = fac.newReference( refe, digest );
194 Element el = document.getElementById(xmlsignparams.getObjectIds());
195 XMLStructure content = new DOMStructure(el);
196 obj = fac.newXMLObject (Collections.singletonList(content), xmlsignparams.getObjectIds(), null, null);
197 } else if (xmlsignparams.isDetached()) {
198 ref = fac.newReference( "http://www.w3.org/TR/xml-stylesheet", digest);
199 } else {
200 log.error("The XML Signature isn't enveloped, enveloping or detached!!!???");
201 return false;
202 }
203
204
205 CanonicalizationMethod canonMethod = fac.newCanonicalizationMethod(xmlsignparams.getCanonical(), (C14NMethodParameterSpec) null);
206 SignatureMethod signMethod = fac.newSignatureMethod(xmlsignparams.getSignatureAlg(), null);
207 SignedInfo si = fac.newSignedInfo( canonMethod, signMethod, Collections.singletonList(ref) );
208
209
210
211 X509Certificate[] signercertchain = userpkandcert.getX509CertificateChain();
212 KeyInfoFactory kif = fac.getKeyInfoFactory();
213
214 List certs = new ArrayList();
215 for ( int i=0; i<signercertchain.length; i++ ) {
216 certs.add(signercertchain[i].getSubjectX500Principal().getName());
217
218
219 certs.add(signercertchain[i]);
220 }
221
222
223 X509Data data = kif.newX509Data(certs);
224 List dataList = Collections.singletonList(data);
225 KeyInfo ki = kif.newKeyInfo(dataList);
226
227
228
229 log.info("XML Signer do not download and insert CRL from X.509 CDP");
230
231
232 log.info("XML Signer do not Timestamp in this release, sorry!");
233
234
235
236
237
238
239 XMLSignature signature = null;
240 Document signedxmldoc = null;
241 DOMSignContext dsc = null;
242
243 if (xmlsignparams.isEnveloping()) {
244
245 signature = fac.newXMLSignature(si, ki, Collections.singletonList(obj), null, null);
246
247
248
249 dsc = new DOMSignContext(userpkandcert.getPrivateKey(), document);
250 } else {
251
252 signature = fac.newXMLSignature(si, ki);
253
254 if (xmlsignparams.isDetached()) {
255
256
257 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
258 dbf.setNamespaceAware(true);
259 try {
260 signedxmldoc = dbf.newDocumentBuilder().newDocument();
261 } catch (ParserConfigurationException e) {
262 log.error(""+e.getLocalizedMessage(),e);
263 }
264
265
266
267
268 dsc = new DOMSignContext(userpkandcert.getPrivateKey(), signedxmldoc);
269
270 }
271 if (xmlsignparams.isEnveloped()) {
272
273
274 dsc = new DOMSignContext(userpkandcert.getPrivateKey(), document.getDocumentElement());
275 }
276 }
277
278
279 log.debug("Processing XML Signature");
280 signature.sign(dsc);
281
282 if (xmlsignparams.isDetached())
283 document = signedxmldoc;
284
285 return true;
286 } catch (NoSuchAlgorithmException e) {
287 log.error(e.getLocalizedMessage(),e);
288 } catch (InvalidAlgorithmParameterException e) {
289 log.error(e.getLocalizedMessage(),e);
290 } catch (MarshalException e) {
291 log.error(e.getLocalizedMessage(),e);
292 } catch (XMLSignatureException e) {
293 log.error(e.getLocalizedMessage(),e);
294 } catch (Exception e) {
295 log.error(e.getLocalizedMessage(),e);
296 }
297 return false;
298 }
299 private TransformParameterSpec getTransformParameterSpec(String transform) {
300 TransformParameterSpec params = null;
301 if (transform.equals(Transform.XPATH))
302 params = new XPathFilterParameterSpec("xPath");
303 else if (transform.equals(Transform.XPATH2))
304 params = new XPathFilter2ParameterSpec(Collections.singletonList(new XPathType("xPath2", XPathType.Filter.INTERSECT)));
305 else if (transform.equals(Transform.XSLT))
306 params = new XSLTTransformParameterSpec(new XSLTStructure());
307 return params;
308
309 }
310
311 private static class XSLTStructure implements XMLStructure {
312 public boolean isFeatureSupported(String feature) { return false; }
313 }
314
315 private static class DTDVerificationDisabled implements EntityResolver {
316
317 public InputSource resolveEntity(String publicId, String systemId)
318 throws SAXException, IOException {
319 log.info("Ignoring " + publicId + ", " + systemId);
320 return new InputSource(new StringReader(""));
321 }
322 }
323
324 }