/** * LICENCIA LGPL: * * Esta librería es Software Libre; Usted puede redistribuirlo y/o modificarlo * bajo los términos de la GNU Lesser General Public License (LGPL) * tal y como ha sido publicada por la Free Software Foundation; o * bien la versión 2.1 de la Licencia, o (a su elección) cualquier versión posterior. * * Esta librería se distribuye con la esperanza de que sea útil, pero SIN NINGUNA * GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o ADECUACIÓN A UN * PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public License (LGPL) para más * detalles * * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) * junto con esta librería; si no es así, escriba a la Free Software Foundation Inc. * 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte * <http://www.gnu.org/licenses/>. * * Copyright 2008 Ministerio de Industria, Turismo y Comercio * */ package es.mityc.firmaJava.libreria.utilidades; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.signature.XMLSignatureInput; import org.apache.xml.security.transforms.TransformationException; import org.apache.xml.security.transforms.Transforms; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import es.mityc.firmaJava.libreria.ConstantesXADES; import es.mityc.firmaJava.libreria.xades.errores.FirmaXMLError; /** * @author Ministerio de Industria, Turismo y Comercio * @version 0.9 beta */ public class UtilidadTratarNodo implements ConstantesXADES { static Log log = LogFactory.getLog(UtilidadTratarNodo.class); private final static String[] IDs = {ID, ID_MINUS, ID_MAYUS}; private static Random rnd = new Random(new Date().getTime()); private final static int RND_MAX_SIZE = 1048576; /** * Devuelve en un array de bytes el contenido de los nodos indicados que sean hijos del documento, y que se ajusten al namespace. * * @param doc documento en el que se buscarán los hijos (en cualquier profundidad) * @param ns namespace en el que deben estar los hijos que se van a buscar (<code>null</code> si el mismo namespace que el nodo raiz) * @param nombreHijos nombre del tag de los hijos que se buscarán * @return byte array con el contenido de los nodos hijos, <code>null</code> si no tiene hijos y no es requerido * @throws FirmaXMLError */ public static byte[] obtenerByteNodo(Document doc, String ns, String nombreHijos) throws FirmaXMLError{ return obtenerByteNodo(doc.getDocumentElement(), ns, nombreHijos); } /** * Devuelve en un array de bytes el contenido de los nodos indicados que sean hijos del nodo padre, y que se ajusten al namespace. * * Equivalente a la ejecución: * <blockquote> * obtenerByteNodo(Element padre, String ns, String nombreHijos, true) * </blockquote> * * @param padre nodo padre del que se buscarán los hijos (en cualquier profundidad) * @param ns namespace en el que deben estar los hijos que se van a buscar (<code>null</code> si el mismo namespace que el nodo padre) * @param nombreHijos nombre del tag de los hijos que se buscarán * @return byte array con el contenido de los nodos hijos, <code>null</code> si no tiene hijos y no es requerido * @throws FirmaXMLError */ public static byte[] obtenerByteNodo(Element padre, String ns, String nombreHijos) throws FirmaXMLError{ return obtenerByteNodo(padre, ns, nombreHijos, true); } /** * Devuelve en un array de bytes el contenido de los nodos indicados que sean hijos del nodo padre, y que se ajusten al namespace. * * @param padre nodo padre del que se buscarán los hijos (en cualquier profundidad) * @param ns namespace en el que deben estar los hijos que se van a buscar (<code>null</code> si el mismo namespace que el nodo padre) * @param nombreHijos nombre del tag de los hijos que se buscarán * @param requerido Si el valor es <code>true</code> y no se encuentra ningún hijo lanzará excepción * @return byte array con el contenido de los nodos hijos, <code>null</code> si no tiene hijos y no es requerido * @throws FirmaXMLError */ public static byte[] obtenerByteNodo(Element padre, String ns, String nombreHijos, boolean requerido) throws FirmaXMLError{ NodeList nodesHijos = null; if (ns == null) ns = padre.getNamespaceURI(); nodesHijos = padre.getElementsByTagNameNS(ns, nombreHijos); log.debug(MSG_NUMERO_FIRMAS_DOCUMENTO + nodesHijos.getLength()); if ((nodesHijos.getLength() == 0) && (requerido)) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8) + ESPACIO + I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_33) + ESPACIO + nombreHijos); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8) + ESPACIO + I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_33) + ESPACIO + nombreHijos); } if (nodesHijos.getLength() > 0) { Transforms t = new Transforms(padre.getOwnerDocument()); try { t.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); } catch (TransformationException e) { log.error(e); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } ByteArrayOutputStream bais = new ByteArrayOutputStream(); for (int i = 0; i < nodesHijos.getLength(); i++) { XMLSignatureInput xmlSignatureInput = new XMLSignatureInput(nodesHijos.item(i)); try { XMLSignatureInput resultado = null; resultado = t.performTransforms(xmlSignatureInput); bais.write(resultado.getBytes()); } catch (TransformationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (CanonicalizationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (IOException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } } if (bais.size() > 0) return bais.toByteArray(); } return null; } /** * Devuelve en un array de bytes el contenido de los nodos indicados que sean hijos del nodo padre, y que se ajusten al namespace. * * @param padre nodo padre del que se buscarán los hijos (sólo en un nivel de profundidad) * @param ns namespace en el que deben estar los hijos que se van a buscar (<code>null</code> si el mismo namespace que el nodo padre) * @param nombreHijos nombre del tag de los hijos que se buscarán * @param tope Elemento en el que se para la búsqueda (no se incluirá en el array de bytes), <code>null</code> si no se quiere tope * @return byte array con el contenido de los nodos hijos, <code>null</code> si no tiene hijos y no es requerido * @throws FirmaXMLError */ public static byte[] obtenerByteNodo(Element padre, String ns, String nombreHijos, Element tope) throws FirmaXMLError { NodeList nodesHijos = null; if (ns == null) ns = padre.getNamespaceURI(); nodesHijos = padre.getChildNodes(); if (nodesHijos.getLength() > 0) { Transforms t = new Transforms(padre.getOwnerDocument()); try { t.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); } catch (TransformationException e) { log.error(e); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } ByteArrayOutputStream bais = new ByteArrayOutputStream(); for (int i = 0; i < nodesHijos.getLength(); i++) { Node nodo = nodesHijos.item(i); // Busca el siguiente elemento if (nodo.getNodeType() != Node.ELEMENT_NODE) continue; // si es el elemento tope para de buscar if (tope != null) { if (tope.isEqualNode(nodo)) break; } // comprueba si es un nodo de los buscados if (!nodo.getLocalName().equals(nombreHijos)) continue; if (ns == null) { if (nodo.getNamespaceURI() != null) continue; } else if (!ns.equals(nodo.getNamespaceURI())) continue; XMLSignatureInput xmlSignatureInput = new XMLSignatureInput(nodo); try { XMLSignatureInput resultado = null; resultado = t.performTransforms(xmlSignatureInput); bais.write(resultado.getBytes()); } catch (TransformationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (CanonicalizationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (IOException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } } if (bais.size() > 0) return bais.toByteArray(); } return null; } /** * Devuelve en un array de bytes el contenido de los nodos indicados que sean hijos del nodo padre, y que se ajusten al namespace. * * @param padre nodo padre del que se buscarán los hijos (sólo en un nivel de profundidad) * @param nombreHijos listado de elementos que se buscarán (pareja de namespace y nombre del elemento) * @param tope Elemento en el que se para la búsqueda (no se incluirá en el array de bytes), <code>null</code> si no se quiere tope * @return byte array con el contenido de los nodos hijos, <code>null</code> si no tiene hijos y no es requerido * @throws FirmaXMLError */ public static byte[] obtenerByteNodo(Element padre, ArrayList<NombreNodo> nombreHijos, Element tope) throws FirmaXMLError { NodeList nodesHijos = null; nodesHijos = padre.getChildNodes(); if (nodesHijos.getLength() > 0) { Transforms t = new Transforms(padre.getOwnerDocument()); try { t.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); } catch (TransformationException e) { log.error(e); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } ByteArrayOutputStream bais = new ByteArrayOutputStream(); for (int i = 0; i < nodesHijos.getLength(); i++) { Node nodo = nodesHijos.item(i); // Busca el siguiente elemento if (nodo.getNodeType() != Node.ELEMENT_NODE) continue; // si es el elemento tope para de buscar if (tope != null) { if (tope.isEqualNode(nodo)) break; } // comprueba si es un nodo de los buscados NombreNodo nombreNodo = new NombreNodo(nodo.getNamespaceURI(), nodo.getLocalName()); if (nombreHijos.indexOf(nombreNodo) == -1) continue; XMLSignatureInput xmlSignatureInput = new XMLSignatureInput(nodo); try { XMLSignatureInput resultado = null; resultado = t.performTransforms(xmlSignatureInput); bais.write(resultado.getBytes()); } catch (TransformationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (CanonicalizationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (IOException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } } if (bais.size() > 0) return bais.toByteArray(); } return null; } /** * Devuelve un listado con los elementos que siendo hijos del nodo padre tienen el nombre indicado y están antes del elemento tope. * * @param padre nodo padre del que se buscarán los hijos (sólo en un nivel de profundidad) * @param tope Elemento en el que se para la búsqueda (no se incluirá en el listado), <code>null</code> si no se quiere tope * @param nombreHijo Namespace y localname de los hijos que se buscarán * @return listado con los elementos encontrados */ public static ArrayList<Element> obtenerNodos(Element padre, Element tope, NombreNodo nombreHijo) { ArrayList<Element> resultado = new ArrayList<Element>(); NodeList nodesHijos = padre.getChildNodes(); for (int i = 0; i < nodesHijos.getLength(); i++) { Node nodo = nodesHijos.item(i); // Busca el siguiente elemento if (nodo.getNodeType() != Node.ELEMENT_NODE) continue; // si es el elemento tope para de buscar if (tope != null) { if (tope.isEqualNode(nodo)) break; } // comprueba si es un nodo de los buscados if (new NombreNodo(nodo.getNamespaceURI(), nodo.getLocalName()).equals(nombreHijo)) resultado.add((Element)nodo); } return resultado; } /** * Devuelve un listado con los elementos que siendo hijos del nodo padre tienen el nombre indicado y están antes del elemento tope. * * @param padre nodo padre del que se buscarán los hijos (sólo en un nivel de profundidad) * @param tope Elemento en el que se para la búsqueda (no se incluirá en el listado), <code>null</code> si no se quiere tope * @param nombreHijos listado de Namespace y localname de los hijos que se buscarán * @return listado con los elementos encontrados * @throws FirmaXMLError */ public static ArrayList<Element> obtenerNodos(Element padre, Element tope, ArrayList<NombreNodo> nombreHijos) throws FirmaXMLError { ArrayList<Element> resultado = new ArrayList<Element>(); NodeList nodesHijos = padre.getChildNodes(); for (int i = 0; i < nodesHijos.getLength(); i++) { Node nodo = nodesHijos.item(i); // Busca el siguiente elemento if (nodo.getNodeType() != Node.ELEMENT_NODE) continue; // si es el elemento tope para de buscar if (tope != null) { if (tope.isEqualNode(nodo)) break; } // comprueba si es un nodo de los buscados if (nombreHijos.indexOf(new NombreNodo(nodo.getNamespaceURI(), nodo.getLocalName())) != -1) resultado.add((Element)nodo); } return resultado; } /** * Devuelve un array de bytes con el contenido de los elementos indicados (tras una canonalización estándar). * * @param nodos listado de elementos * @return array de bytes */ public static byte[] obtenerByte(ArrayList<Element> nodos) throws FirmaXMLError { if ((nodos == null) || (nodos.size() == 0)) return null; Transforms t = new Transforms(nodos.get(0).getOwnerDocument()); try { t.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); } catch (TransformationException e) { log.error(e); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } ByteArrayOutputStream bais = new ByteArrayOutputStream(); Iterator<Element> it = nodos.iterator(); while (it.hasNext()) { Element nodo = it.next(); XMLSignatureInput xmlSignatureInput = new XMLSignatureInput(nodo); try { XMLSignatureInput resultado = null; resultado = t.performTransforms(xmlSignatureInput); bais.write(resultado.getBytes()); } catch (TransformationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (CanonicalizationException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } catch (IOException ex) { log.error(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_34), ex); throw new FirmaXMLError(I18n.getResource(LIBRERIAXADES_FIRMAXML_ERROR_8)); } } if (bais.size() > 0) return bais.toByteArray(); return null; } /** * Devuelve un listado con las ID de los elementos. Si no encuentra un atributo que sea ID, busca entre los atributos alguno que tenga * la <i>forma</i> de ID. * * @param elementos listado con los elementos de los cuales obtener las IDs * @return */ public static ArrayList<String> obtenerIDs(ArrayList<Element> elementos) { if (elementos == null) return null; ArrayList<String> resultado = new ArrayList<String>(); Iterator<Element> it = elementos.iterator(); while (it.hasNext()) { Element elemento = it.next(); boolean encontrado = false; NamedNodeMap map = elemento.getAttributes(); for (int i = 0; i < map.getLength(); i++) { Attr attr = (Attr)map.item(i); if (attr.isId()) { resultado.add(attr.getValue()); encontrado = true; break; } } if (!encontrado) { for (int i = 0; i < IDs.length; i++) { if (elemento.hasAttribute(IDs[i])) { resultado.add(elemento.getAttribute(IDs[i])); break; } } } } return resultado; } /** * Busca en una lista de nodos un elemento que tenga la id indicada * * @param id * @return */ public static Element getElementById(NodeList list, String id) { Element resultado = null; if (list != null) { int length = list.getLength(); for (int i = 0; i < length; i++) { Node node = list.item(i); // AppPerfect: Falso positivo if (node.getNodeType() == Node.ELEMENT_NODE) { Element el = (Element) node; if (id.equals(el.getAttribute(ID))) { resultado = el; break; } } } } return resultado; } /** * Explora el elemento y sus hijos para obtener un elemento que tenga la Id indicada * * @param el * @param id * @return */ private static Element exploreElementById(Element el, String id) { for (int i = 0; i < IDs.length; i++) { if (id.equals(el.getAttribute(IDs[i]))) { return el; } } // explora los hijos del nodo NodeList nodes = el.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node nodo = nodes.item(i); if (nodo.getNodeType() == Node.ELEMENT_NODE) { Element temp = exploreElementById((Element)nodo, id); if (temp != null) return temp; } } return null; } /** * Busca un nodo que tenga la Id indicada. Busca la id en cualquier atributo que tenga la forma Id, ID, ó id. * * @param doc * @param id * @return el elemento con la id indicada, <code>null</code> si no hay ningún elemento con esa id. */ public static Element getElementById(Document doc, String id) { if ((doc == null) || (id == null)) return null; Element el = doc.getElementById(id); if (el == null) { el = exploreElementById(doc.getDocumentElement(), id); } return el; } /** * Busca un nodo que tenga la Id indicada que sea hijo del nodo indicado. Busca la id en cualquier atributo que tenga la forma Id, ID, ó id. * * @param doc * @param id * @return el elemento con la id indicada, <code>null</code> si no hay ningún elemento con esa id. */ public static Element getElementById(Element padre, String id) { Element el = getElementById(padre.getOwnerDocument(), id); // Comprueba que el nodo encontrado es hijo if (el != null) { Node temp = padre; while ((temp != null) && (!temp.isSameNode(padre))) temp = temp.getParentNode(); if (temp != null) return el; } return null; } /** * Genera una nueva ID que no esté siendo usada en el documento * * @param doc * @param prefix * @return */ public static String newID(Document doc, String prefix) { String newID = prefix + rnd.nextInt(RND_MAX_SIZE); while (getElementById(doc, newID) != null) newID = prefix + rnd.nextInt(RND_MAX_SIZE); return newID; } }