View Javadoc

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  	 * Sign an xml file from eparapher signature settings object
74  	 * 
75  	 * @param xmlSourceFile
76  	 * @param cmssignparams
77  	 * @return signedfile path to the file
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  	 * Save XML Document
91  	 */
92  	private String saveSignedXmlFile( String xmlSourceFile, XMLSignatureParameters xmlsignparams ) {
93  		String xmlDestFile;
94  		
95  		//Determine where to save file.
96  		if ( EParapherManager.getInstance().getSettings().isXMLSignatureReplaceFile() ) {
97  			File original = new File(xmlSourceFile);
98  			//TODO : 4 Undo/Redo : move to temporary dir
99  			original.delete();
100 			xmlDestFile = xmlSourceFile ;
101 		} else {
102 			xmlDestFile= xmlSourceFile + ".signed.xml";
103 		}
104 		
105     	// output the resulting document
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 		// Instantiate the document to be validated
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 			// Disable DTD Validation
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 		//FIXME : Enveloping + XSLT Transform + Base 64
173         // Create a DOM XMLSignatureFactory that will be used to generate the enveloped signature
174 		log.debug("Preparing XML Signature");
175 		XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
176 
177 	    // Create a Reference to the enveloped document and
178 		// also specify the digest algorithm and the ENVELOPED Transform.
179 		Reference ref = null;
180 		XMLObject obj = null;
181 		try {
182 			DigestMethod digest = fac.newDigestMethod(xmlsignparams.getDigestAlg(), null);
183 			if (xmlsignparams.isEnveloped()) {
184 				//in this case we are signing the whole document, so a URI of "" signifies that)
185 				List listTransform = Collections.singletonList(fac.newTransform(xmlsignparams.getTransform(), getTransformParameterSpec(xmlsignparams.getTransform()) ));
186 				//TODO : select a document part/reference
187 				String reference="";
188 				ref = fac.newReference(reference, digest, listTransform, null, null);
189 			} else if (xmlsignparams.isEnveloping()) {
190 		        // Next, create a Reference to a same-document URI that is an Object
191 		        // element and specify the digest algorithm
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 			// Create the SignedInfo
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 			// Create a KeyValue containing the RSA PublicKey that was generated
211 			X509Certificate[] signercertchain = userpkandcert.getX509CertificateChain();
212 			KeyInfoFactory kif = fac.getKeyInfoFactory();
213 			// Insert the certificate
214 			List certs = new ArrayList();
215 			for ( int i=0; i<signercertchain.length; i++ ) {
216 				certs.add(signercertchain[i].getSubjectX500Principal().getName());
217 				//certs.add(signercertchain[i].getIssuerX500Principal().getName());
218 				//certs.add(signercertchain[i].getSubjectAlternativeNames());
219 				certs.add(signercertchain[i]);
220 			}
221 			
222 			// charger les donnees du certificat
223 			X509Data data = kif.newX509Data(certs);
224 			List dataList = Collections.singletonList(data);
225 			KeyInfo ki = kif.newKeyInfo(dataList);
226 			
227 			//TODO : v0.2, Download CRLs and insert them in the Signature
228 			
229 			log.info("XML Signer do not download and insert CRL from X.509 CDP");
230 			
231 			//TODO : Timestamp 
232 			log.info("XML Signer do not Timestamp in this release, sorry!");
233 			
234 			//Insert the Public Key
235 			//KeyValue kv = kif.newKeyValue(signercert.getPublicKey());
236 			// Create a KeyInfo and add the KeyValue to it
237 	        //KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
238 			
239 			XMLSignature signature = null;
240 			Document signedxmldoc = null;
241 			DOMSignContext dsc = null;
242 			
243 			if (xmlsignparams.isEnveloping()) {
244 				// Create the XMLSignature (but don't sign it yet)
245 				signature = fac.newXMLSignature(si, ki, Collections.singletonList(obj), null, null); 
246 
247 			    // Create a DOMSignContext and specify the DSA PrivateKey for signing
248 				// and the document location of the XMLSignature
249 			    dsc = new DOMSignContext(userpkandcert.getPrivateKey(),  document);
250 			} else {
251 				// Create the XMLSignature (but don't sign it yet)
252 				signature = fac.newXMLSignature(si, ki);
253 				
254 				if (xmlsignparams.isDetached()) {
255 
256 					// Create the Document that will hold the resulting XMLSignature
257 					DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
258 					dbf.setNamespaceAware(true); // must be set
259 					try {
260 						signedxmldoc = dbf.newDocumentBuilder().newDocument();
261 					} catch (ParserConfigurationException e) {
262 						log.error(""+e.getLocalizedMessage(),e);
263 					}
264 
265 					// Create a DOMSignContext and set the signing Key to the DSA 
266 				    // PrivateKey and specify where the XMLSignature should be inserted 
267 					// in the target document (in this case, the document root)
268 					dsc = new DOMSignContext(userpkandcert.getPrivateKey(), signedxmldoc);
269 
270 				}
271 				if (xmlsignparams.isEnveloped()) {
272 				    // Create a DOMSignContext and specify the PrivateKey and
273 				    // location of the resulting XMLSignature's parent element
274 					dsc = new DOMSignContext(userpkandcert.getPrivateKey(), document.getDocumentElement());
275 				}
276 			}
277 
278 	        // Marshal, generate (and sign) the enveloped signature
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 	//TODO : move
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 }