View Javadoc

1   /**
2    * LICENCIA LGPL:
3    * 
4    * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
5    * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
6    * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
7    * la Licencia, o (a su elección) cualquier versión posterior.
8    * 
9    * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
10   * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
11   * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
12   * License (LGPL) para más detalles
13   * 
14   * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
15   * junto con esta librería; si no es así, escriba a la Free Software Foundation 
16   * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
17   * <http://www.gnu.org/licenses/>.
18   *
19   * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
20   */
21  package es.accv.arangi.base.mityc;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.net.URL;
26  import java.security.PrivateKey;
27  import java.security.cert.X509Certificate;
28  import java.util.ArrayList;
29  
30  import javax.xml.parsers.DocumentBuilder;
31  import javax.xml.parsers.DocumentBuilderFactory;
32  import javax.xml.xpath.XPath;
33  import javax.xml.xpath.XPathConstants;
34  import javax.xml.xpath.XPathExpression;
35  import javax.xml.xpath.XPathFactory;
36  
37  import org.apache.log4j.Logger;
38  import org.apache.xml.security.utils.IgnoreAllErrorHandler;
39  import org.w3c.dom.Attr;
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  import org.xml.sax.InputSource;
45  
46  import es.accv.arangi.base.exception.signature.CounterSignatureException;
47  import es.accv.arangi.base.signature.XAdESSignature;
48  import es.accv.arangi.base.util.Util;
49  import es.mityc.firmaJava.libreria.ConstantesXADES;
50  import es.mityc.firmaJava.libreria.excepciones.AddXadesException;
51  import es.mityc.firmaJava.libreria.utilidades.I18n;
52  import es.mityc.firmaJava.libreria.utilidades.NombreNodo;
53  import es.mityc.firmaJava.libreria.utilidades.UtilidadTratarNodo;
54  import es.mityc.firmaJava.libreria.xades.DataToSign;
55  import es.mityc.firmaJava.libreria.xades.FirmaXML;
56  import es.mityc.javasign.xml.refs.AbstractObjectToSign;
57  import es.mityc.javasign.xml.refs.InternObjectToSign;
58  import es.mityc.javasign.xml.refs.ObjectToSign;
59  import es.mityc.javasign.xml.refs.SignObjectToSign;
60  
61  
62  /**
63   * Clase para realizar contrafirmas en firmas XML 
64   * 
65   * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
66   */
67  public class ContraFirmaXML{
68  
69  	static Logger logger = Logger.getLogger(ContraFirmaXML.class);
70  	
71      /**
72       * Contrafirma una firma según esquema XAdES. La contrafirma se realiza sobre
73       * la última firma en caso de haber más de una.
74       * 
75       * @param certificadoFirma Certificado para realizar la contrafirma
76       * @param xml XAdES
77       * @param pk Clave privada con la que se realizará la firma
78  	 * @param digitalSignatureAlgorithm Algoritmo de firma (si nulo algoritmo por defecto)
79  	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
80  	 * 	(puede ser nulo)
81       * @param xadesSchema Esquema XAdES a utilizar
82       */
83      public static Document counterSign(X509Certificate certificadoFirma,
84      		DataToSign xml, PrivateKey pk, String xadesSchema, 
85      		String digitalSignatureAlgorithm, URL urlTSA) throws Exception {
86      
87      	return counterSign(certificadoFirma, xml, null, pk, digitalSignatureAlgorithm, urlTSA, xadesSchema);
88      }
89      
90      /**
91       * Contrafirma una firma según esquema XAdES. La contrafirma se realiza sobre
92       * la firma cuyo certificado se pasa como parámetro en 'certificadoContraFirma'.
93       * 
94       * @param certificadoFirma Certificado para realizar la contrafirma
95       * @param xml XAdES
96       * @param certificadoContraFirma Certificado de la firma que se contrafirmará
97       * @param pk Clave privada con la que se realizará la firma
98  	 * @param digitalSignatureAlgorithm Algoritmo de firma (si nulo algoritmo por defecto)
99  	 * @param urlTSA URL del servidor de sello de tiempos para incluir en la contrafirma
100 	 * 	(puede ser nulo)
101      * @param xadesSchema Esquema XAdES a utilizar
102      * @throws CounterSignatureException Errores durante la contrafirma
103      */
104     public static Document counterSign(X509Certificate certificadoFirma,
105     		DataToSign xml, X509Certificate certificadoContraFirma, PrivateKey pk, 
106     		String digitalSignatureAlgorithm, URL urlTSA, String xadesSchema) 
107     				throws CounterSignatureException {
108 
109     	try {
110 	    	Document doc = xml.getDocument();
111 	    	if (doc == null) {
112 		        try {
113 		        	InputStream is = xml.getInputStream();
114 		        	if (is != null) {
115 		            	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
116 		            	dbf.setNamespaceAware(true);
117 		            	DocumentBuilder db = dbf.newDocumentBuilder();
118 		                db.setErrorHandler(new IgnoreAllErrorHandler());
119 			            InputSource isour = new InputSource(is);
120 			            String encoding = xml.getXMLEncoding();
121 			            isour.setEncoding(encoding);
122 			            doc = db.parse(isour);
123 		        	}
124 		        } catch (IOException ex) {
125 		        	throw new Exception(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_50));
126 		        }
127 	    	}
128 	    	
129 	    	// Si no se indica nodo a contrafirmar se contrafirma la última firma disponible
130 	    	Node nodePadreNodoFirmar = null;
131 	    	if (certificadoContraFirma != null) {
132 	        	nodePadreNodoFirmar = buscarNodoAFirmar(doc, certificadoContraFirma);
133 	        	if(nodePadreNodoFirmar == null) {
134 		    		logger.info(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_33));
135 		    		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_51));
136 	        	}
137 	    	} 
138 	    	
139 	    	if (nodePadreNodoFirmar == null) {
140 	    		// Busca la última firma
141 	    		NodeList list = doc.getElementsByTagNameNS(ConstantesXADES.SCHEMA_DSIG, ConstantesXADES.SIGNATURE);
142 	    		if (list.getLength() < 1) {
143 	        		logger.info(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_33));
144 	        		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_51));
145 	    		} else {
146 	    			nodePadreNodoFirmar = list.item(list.getLength() - 1);
147 	    		}
148 	    	}
149 	    	String idSignatureValue = null;
150 	    	Element padreNodoFirmar = null;
151 	    	if ((nodePadreNodoFirmar != null) && (nodePadreNodoFirmar.getNodeType() == Node.ELEMENT_NODE)) {
152 	    		padreNodoFirmar = (Element)nodePadreNodoFirmar;
153 	        	ArrayList<Element> listElements = UtilidadTratarNodo.obtenerNodos(padreNodoFirmar, 2, new NombreNodo(ConstantesXADES.SCHEMA_DSIG, ConstantesXADES.SIGNATURE_VALUE));
154 	        	if (listElements.size() != 1) {
155 	        		// TODO: indicar un error específico (No se puede tener más de un nodo SignatureValue por firma XmlDSig)
156 	        		logger.info(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_33));
157 	        		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_51));
158 	        	}
159 	        	idSignatureValue = listElements.get(0).getAttribute(ConstantesXADES.ID);
160 	        	// TODO: Si este nodo no tiene id, identificarlo vía XPATH
161 	        	if (idSignatureValue == null) {
162 	        		// TODO: indicar un error específico (No se puede identificar nodo SignatureValue en firma XmlDSig)
163 	        		logger.info(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_33));
164 	        		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_51));
165 	        	}
166 	    	}
167 	
168 	    	// Se busca si existe el path hasta el nodo raíz CounterSignature. Si no existe, se crea.
169 	    	ArrayList<Element> listElements = UtilidadTratarNodo.obtenerNodos(padreNodoFirmar, 2, ConstantesXADES.QUALIFYING_PROPERTIES);
170 	    	if (listElements.size() != 1) {
171 	    		// TODO: indicar un error específico (No se puede tener más de un nodo Qualifying por firma XAdES 
172 	    		logger.info(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_33));
173 	    		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_51));
174 	    	}
175 	    	String esquemaOrigen = listElements.get(0).getNamespaceURI();
176 	    	NodeList nodosUnsigSigProp = (padreNodoFirmar).getElementsByTagNameNS(esquemaOrigen, 
177 	    			ConstantesXADES.UNSIGNED_SIGNATURE_PROPERTIES);
178 	    	
179 	    	Element nodoRaiz = null;
180 	    	if (nodosUnsigSigProp != null && nodosUnsigSigProp.getLength() != 0)
181 	    		nodoRaiz = (Element)nodosUnsigSigProp.item(0); // Se toma el primero de la lista
182 	    	else { // Se busca el nodo QualifyingProperties
183 	    		NodeList nodosQualifying = (padreNodoFirmar).getElementsByTagNameNS(esquemaOrigen, ConstantesXADES.QUALIFYING_PROPERTIES);
184 	        	
185 	        	if (nodosQualifying != null && nodosQualifying.getLength() != 0) {
186 	        		Element nodoQualifying = (Element)nodosQualifying.item(0);
187 	        		Element unsignedProperties = null;
188 	        		if (nodoQualifying.getPrefix() != null) {
189 	        			unsignedProperties =
190 	        				doc.createElementNS(esquemaOrigen, nodoQualifying.getPrefix() +
191 	        						ConstantesXADES.DOS_PUNTOS + ConstantesXADES.UNSIGNED_PROPERTIES);
192 	        			nodoRaiz = doc.createElementNS(esquemaOrigen, nodoQualifying.getPrefix() +
193 	        					ConstantesXADES.DOS_PUNTOS + ConstantesXADES.UNSIGNED_SIGNATURE_PROPERTIES);
194 	        		} else {
195 	        			unsignedProperties =
196 	        				doc.createElementNS(esquemaOrigen, ConstantesXADES.UNSIGNED_PROPERTIES);
197 	        			nodoRaiz = doc.createElementNS(esquemaOrigen, ConstantesXADES.UNSIGNED_SIGNATURE_PROPERTIES);
198 	        		}
199 	        		
200 	        		unsignedProperties.appendChild(nodoRaiz);
201 	        		nodosQualifying.item(0).appendChild(unsignedProperties);       		
202 	        	} else
203 	        		throw new AddXadesException(I18n.getResource(ConstantesXADES.LIBRERIAXADES_FIRMAXML_ERROR_52));
204 	    	}
205 	    		
206 	    	// Se genera un nuevo nodo Countersignature donde irá la firma
207 	    	Element counterSignature = null;
208 			if (nodoRaiz.getPrefix() != null) {
209 				counterSignature = doc.createElementNS(esquemaOrigen, nodoRaiz.getPrefix() +
210 					ConstantesXADES.DOS_PUNTOS + ConstantesXADES.COUNTER_SIGNATURE);
211 			} else { 
212 				counterSignature = doc.createElementNS(esquemaOrigen, ConstantesXADES.COUNTER_SIGNATURE);
213 			}
214 			nodoRaiz.appendChild(counterSignature);
215 	
216 	    	
217 	    	// Se escribe una Id única
218 	    	Attr counterSignatureAttrib = doc.createAttributeNS(null, ConstantesXADES.ID);
219 	    	String counterSignatureId = UtilidadTratarNodo.newID(doc, ConstantesXADES.COUNTER_SIGNATURE + ConstantesXADES.GUION);
220 	    	counterSignatureAttrib.setValue(counterSignatureId);
221 	    	counterSignature.getAttributes().setNamedItem(counterSignatureAttrib);
222 	    	
223 	    	// Se reemplaza el documento original por el documento preparado para contrafirma
224 	    	xml.setDocument(doc);
225 	    	
226 	        // Se incluye la referencia a la contrafirma
227 	        AbstractObjectToSign obj = null;
228 			if (XAdESSignature.DEFAULT_XADES_SCHEMA_URI.equals(xadesSchema)) {
229 				obj = new SignObjectToSign(idSignatureValue);
230 			} else {
231 				obj = new InternObjectToSign(idSignatureValue);
232 			}
233 	        xml.addObject(new ObjectToSign(obj, null, null, null, null));		
234 	
235 	
236 	        // Se firma el documento generado, indicando el nodo padre y el identificador del nodo a firmar
237 	        xml.setParentSignNode(counterSignatureId);
238 	        FirmaXML firma = new FirmaXML();
239 	        if (urlTSA != null) {
240 	        	firma.setTSA(urlTSA.toString());
241 	        }
242 	    	Object[] res = firma.signFile(certificadoFirma, xml, pk, XAdESUtil.getXAdESDigitalSignatureAlgorithm(digitalSignatureAlgorithm), null);
243 			
244 	
245 	    	doc = (Document) res[0];
246 	    	
247 	    	// Se elimina el identificador del nodo CounterSignature
248 	    	counterSignature = UtilidadTratarNodo.getElementById(doc, counterSignatureId);
249 	    	counterSignature.removeAttribute(ConstantesXADES.ID);
250 	    	
251 	    	return doc;
252     	} catch (Exception e) {
253     		throw new CounterSignatureException (e);
254     	}
255     }
256 
257     /*
258      * Busca el nodo signature cuyo certificado es igual al pasado como parámetro
259      */
260 	private static Node buscarNodoAFirmar(Document doc, X509Certificate certificadoContraFirma) {
261 		//-- Localizar los certificados de la firma
262 		XPathFactory factory = XPathFactory.newInstance();
263 		XPath xpath = factory.newXPath();
264 		NodeList certificateNodes;
265 		try {
266 			XPathExpression expr = xpath.compile("//*[local-name()='Signature']/*[local-name()='KeyInfo']/*[local-name()='X509Data']/*[local-name()='X509Certificate']");
267 			certificateNodes = (NodeList) expr.evaluate (doc, XPathConstants.NODESET);
268 		} catch (Exception e) {
269 			logger.info ("[ContraFirmaXML.buscarNodoAFirmar]::Error inesperado", e);
270 			return null;
271 		}
272 
273 		if (certificateNodes == null || certificateNodes.getLength() == 0) {
274 			logger.info("[ContraFirmaXML.buscarNodoAFirmar]::Falta el elemento 'X509Certificate' de la firma, con lo que no es posible obtener la cadena de confianza de un certificado que no existe");
275 			return null;
276 		}
277 		
278 		//-- Encontrar el certificado
279 		for (int i = 0; i < certificateNodes.getLength(); i++) {
280 			try {
281 				X509Certificate x509Cert = Util.getCertificate(Util.decodeBase64(certificateNodes.item(i).getTextContent()));
282 				if (x509Cert.equals (certificadoContraFirma)) {
283 					return certificateNodes.item(i).getParentNode().getParentNode().getParentNode();
284 				}
285 			} catch (Exception e) {
286 				logger.info ("[ContraFirmaXML.buscarNodoAFirmar]::Error inesperado", e);
287 			}
288 		}
289 		
290 		return null;
291 	}
292 
293 	
294 }