Java tutorial
/* Copyright (C) 2007 Flix Garca Borrego (borrego at gmail.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package org.viafirma.nucleo.firma.imp; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.utils.Constants; import org.apache.xml.security.utils.XMLUtils; import org.apache.xpath.XPathAPI; import org.bouncycastle.util.encoders.Base64; import org.viafirma.cliente.firma.TypeFormatSign; import org.viafirma.excepciones.CodigoError; import org.viafirma.nucleo.Nucleo; import org.viafirma.nucleo.firma.FirmaBridge; import org.viafirma.util.Constantes; import org.viafirma.util.FaceletsUtil; import org.viafirma.util.XmlSignUtil; import org.viafirma.vo.Documento; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.viavansi.framework.core.util.FechaUtilities; /** * Implementacin especfica encargada de la firma de ficheros por el cliente * * Esta implementacin se basa en el uso de un Applet para recuperar que muestra * el fichero que se desea firmar * * @author Felix Garcia Borrego (borrego at gmail.com) * @author Alexis Castilla Armero (Pencerval at gmail.com) */ public class FirmaOpenocesAppletBridgeImp extends HttpServlet implements FirmaBridge { /** * */ private static final long serialVersionUID = 1L; /** * Logger. */ private Log log = LogFactory.getLog(FirmaOpenocesAppletBridgeImp.class); /** * URI a la que se redirecciona para la firma del fichero */ public static final String URI_FIRMA = "/firma/openOcesAppletSign.jsf"; /** * Nombre del parametro que se coloca en la url para indicar que se desea * firma de tiempo. */ public static final String PARAM_SIGNTIME = "signtime"; public static final String PARAM_TO_FILE = "toFile"; /** * Nombre de la variable/parametro en el que se almacena temporalmente el * identificador del documento. */ private static final String DOCUMENT_TO_SIGN_ID = "documentToSignId"; private static FirmaOpenocesAppletBridgeImp singleton = new FirmaOpenocesAppletBridgeImp(); /** * */ public FirmaOpenocesAppletBridgeImp() { // debe ser pblica por ser un Servlet( esto hace que el singleton se // vea modificado) } /** * Recupera la nica instancia del bridge. Basandose en el patron singleton * * @return Instancia de la bridge */ public static FirmaOpenocesAppletBridgeImp getInstance() { return singleton; } /*************************************************************************** * Para solicitar el certificado redirecciona al cliente a una pgina que * contiene un Applet que solicita al usuario su certificado. * * @see org.viafirma.nucleo.autenticacion.AutenticacionBridge#generarSolicitudCertificado(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ public void generarSolicitudFirma(Documento documento, HttpServletRequest request, HttpServletResponse response) { log.debug("Iniciando solicitud de firma de documeto"); // almaceno el identificador de sessin( posteriormente se utiliza para // cachear los diferentes certificados) request.setAttribute(Nucleo.SESSION_ID, request.getSession().getId()); request.setAttribute(DOCUMENT_TO_SIGN_ID, documento.getId()); // Procesamos del formato Xades //preprocessXadesEpesEnveloped(documento, request); String shaHash = new String( Base64.encode(org.apache.commons.codec.digest.DigestUtils.sha(documento.getDatos()))); StringBuffer sb = new StringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"utf8\" ?>"); sb.append("<attachments>"); sb.append("<attachment>"); sb.append("<title>").append(documento.getNombre()).append("</title>"); sb.append("<path>").append(getRequestDownload(request, documento)).append("</path>"); sb.append("<mimeType>").append(documento.getTipo().getMimetype()).append("</mimeType>"); sb.append("<size>").append(documento.getSize()).append("</size>"); sb.append("<hashValue>").append(shaHash).append("</hashValue>"); if (TypeFormatSign.XADES_EPES_ENVELOPED.equals(documento.getTypeFormatSign())) { sb.append("<signTime>").append(getSignTime()).append("</signTime>"); } // if (isOptional) { // sb.append("<optional/>"); // } sb.append("</attachment>"); sb.append("</attachments>"); // meto en request el parametro attachments request.setAttribute("attachments", new String(Base64.encode(sb.toString().getBytes()))); try { request.setAttribute("signtext", new String(Base64.encode(("Firma del documento: " + documento.getNombre()).getBytes("UTF-8")))); } catch (UnsupportedEncodingException e) { log.error("No se puede codificar el texto para ser firmado.", e); } FaceletsUtil.forward(request, response, URI_FIRMA); } /** * Preprocesa el documento que se va a enviar al usuario para su firma. El * documento es canonizado y se eliminan todos los elementos Signature que * aparecen en el * * @param documento * @param request */ public void preprocessXadesEpesEnveloped(Documento documento, HttpServletRequest request) { if (TypeFormatSign.XADES_EPES_ENVELOPED.equals(documento.getTypeFormatSign())) { request.setAttribute("viafirmaSignFormat", TypeFormatSign.XADES_EPES_ENVELOPED.name()); // Parsea el documento original para buscar posibles elementos de // tipo // Signature InputSource ioXml = new InputSource(new ByteArrayInputStream(documento.getDatos())); javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE); javax.xml.parsers.DocumentBuilder db; try { db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.parse(ioXml); // Recuperamos todos los elementos de tipo signature. Element nscontext = XMLUtils.createDSctx(doc, "ds", Constants.SignatureSpecNS); NodeList nodeList = XPathAPI.selectNodeList(doc, "//ds:Signature", nscontext); // Extrae los hijos que ds:signature for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); node.getParentNode().removeChild(node); } if (nodeList.getLength() > 0) { log.debug( "Hay Signatures eliminados del documento XAdes que deberan ser aadidos posteriormente."); request.getSession().setAttribute(Constantes.SIGNATURE_PRINCIPAL, nodeList.item(0)); } // Debido a dificutades en la canonizacin desde el applet le // pasamos al // applet un xml ya canonizado para facilitar el proceso. documento.setDatos(XmlSignUtil.getInstance().canonizar(doc)); } catch (ParserConfigurationException e) { log.warn("Problemas al prerocesar el XAdes.", e); } catch (SAXException e) { log.warn("Problemas al prerocesar el XAdes.", e); } catch (IOException e) { log.warn("Problemas al prerocesar el XAdes.", e); } catch (TransformerException e) { log.warn("Problemas al prerocesar el XAdes.", e); } } } /** * Retorna una url vlida desde la que descargar el documento que se desea * firmar * * @param request * @param documento * @return */ private String getRequestDownload(HttpServletRequest request, Documento documento) { String path = StringUtils.substringBeforeLast(request.getRequestURI(), "/conectorFirmaOpenId"); path += "/firma/openocesAppletBridgeImp"; path += "?" + Constantes.PARAM_DOWNLOAD + "=" + documento.getId(); // Fix: Hace que el applet detecte correctamente el nombre del fichero // en modo descarga path += "/" + documento.getNombre(); return path; } /** * Retorna el tiempo de firma del documento * * @return time */ private String getSignTime() { Date date = new Date(); String time = FechaUtilities.generarXMLDateTime(date); return time; } /** * Este mtodo es invocado por el Applet de autenticacin cuando completa la * autenticacin. El mtodo recupera el identificador de sessin para * identificar este identificador con la peticin y redirecciona al nucleo * para que procese el certificado. * * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String resultado = request.getParameter("result"); Nucleo nucleo = Nucleo.getInstance(); if (request.getParameter(Constantes.PARAM_DOWNLOAD) != null) { // obtengo el identificador del documento que deseamos descargar String id = request.getParameter(Constantes.PARAM_DOWNLOAD); // H9DZQTGSE1188069204941/fichero.pdf --> H9DZQTGSE1188069204941 id = StringUtils.substringBefore(id, "/"); // descargo el fichero Documento documento = Nucleo.getInstance().getDocumentoCacheado(id); if (documento != null) { if (request.getParameter(PARAM_TO_FILE) != null) { response.addHeader("Content-Disposition", "attachment; filename=\"" + documento.getNombre() + "\""); response.addHeader("Content-Transfer-Encoding", "binary"); response.setContentType("file"); } else { response.addHeader("Content-Disposition", "filename=\"" + documento.getNombre() + "\";"); response.setContentType(documento.getTipo().getMimetype()); } response.getOutputStream().write(documento.getDatos()); response.flushBuffer(); response.getOutputStream().close(); } } else if ("OK".equalsIgnoreCase(resultado)) { // El applet recupero correctamente el certificado y este fue // almacenado correctamente por el nucleo log.debug("El certificado ha sido ya recuperado, iniciamos su validacin"); String sessionID = request.getParameter(Nucleo.SESSION_ID); String idFirma = request.getParameter(DOCUMENT_TO_SIGN_ID); // PostProcesamos el XmlDocument Documento documentoOriginal = Nucleo.getInstance().getDocumentoCacheado(idFirma); Document xmlDocument = Nucleo.getInstance().getXMLDocument(sessionID); // post procesamos el documento para aadir los Siginature que // pudiera haber tenido previamente. postprocessXadesEpesEnveloped(xmlDocument, documentoOriginal, request); // comprobamos el certificado y si todo esta correcto // redireccionamos nucleo.finFirma(sessionID, idFirma, request, response); } else if ("errorVerificacion".equalsIgnoreCase(resultado)) { // El certificado no es vlido nucleo.redireccionarError(CodigoError.ERROR_CERTIFICADO_NO_VALIDO, request, response); } else if ("error".equalsIgnoreCase(resultado)) { // se ha producido algun error lo enviamos a una pgina de error // informando de lo sucedido nucleo.redireccionarError(CodigoError.ERROR_CERTIFICADO_NO_FOUND, request, response); } else if ("cancel".equalsIgnoreCase(resultado)) { // El usuario ha cancelado y no ha enviado ningun certificado al // servidor. nucleo.redireccionarError(CodigoError.WARNING_CERTIFICADO_NO_ENVIADO, request, response); } else { log.error("Resultado no esperado:" + resultado); throw new ServletException("Resultado no esperado :" + resultado); } } /** * Este mtodo es invocado por el Applet de autenticacin cuando recupera el * certificado. Recuperamos el certificado enviado por el applet( dentro de * un XML-sig), lo procesamos y lo almacenamod en la cache para que sea * recuperado posteriormente por el Nucleo. * * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String resultado = request.getParameter("result"); if ("OK".equalsIgnoreCase(resultado)) { // el applet ha podido recuperar el certificado correctamente, // iniciamos el proceso de autenticacin y validacin log("El usuario ha firmado. Cacheamos el resultado en el Nucleo."); tratarDatosRecuperados(request, response); } else { // el certificado no ha podido ser recuperado por el certificado log("El usuario no ha indicado un certificado"); } } /** * Recupera el certificado y el resultado de la firmaenviado por el * navegador y lo almacena en el nucleo. * * Proceso: * * <pre> * 1.- Recupera el XmlSignature desde el parametro message * 2.- Desde el XmlSignature recupera el X509Certificado * 3.- Desde el xmlSignature envia el xmlSignature completo al nucleo para que este lo persista. * 3.- Se lo pasa al ncleo para que lo valide. * </pre> * * @param request * @param response * @throws ServletException */ private void tratarDatosRecuperados(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { // certificado en xml codificado en base 64 String xmldoc_b64 = request.getParameter("message"); // recupero el identificador de sessin con el que posteriormente // poder identificar este objeto. String sessionID = request.getParameter("sessionID"); String idFirma = request.getParameter(DOCUMENT_TO_SIGN_ID); XmlSignUtil xmlSignParser = XmlSignUtil.getInstance(); // Recuperamos el documento que ha sido firmado Document xmlDocument = xmlSignParser.getDocumentFromBase64(xmldoc_b64); // Recupero el XMLSignature generado por el cliente List<XMLSignature> listXMLSignatureFirmados = xmlSignParser.getXMLSignatureFormDocument(xmlDocument); // obtengo el xml signature(resultado de la firma) // El usuario solo va ha hacer una firma XMLSignature signature = listXMLSignatureFirmados.get(0); // recupero el XmlSignature generado por el applet de autenticacin X509Certificate certificadoX509 = xmlSignParser.getX509(signature); // mostramos el certificado recuperado if (log.isDebugEnabled()) { log.debug("Firma realizada con el certificado certificado " + certificadoX509.getIssuerDN().getName()); } // enviamos el certificado al ncleo para que este compruebe la // validez del mismo Nucleo nucleo = Nucleo.getInstance(); // almacena el certificado recuperado en el ncleo para su // procesamiento posterior. nucleo.cachearCertificado(sessionID, certificadoX509); // cacheamos el xmlsignature recien obtenido nucleo.cachearXmlDocument(sessionID, xmlDocument); } catch (Exception e) { throw new ServletException(e); } } /** * Postprocesamiento del XML ya firmado por el usuario. En el caso de que el * documento original tuviese zonas Signature, se le vuelven a adjuntar, de * forma de que el nueo quede el primero. * * @param documento * @param request * @throws TransformerException */ private void postprocessXadesEpesEnveloped(Document xmlDocument, Documento documentoOriginal, HttpServletRequest request) { if (TypeFormatSign.XADES_EPES_ENVELOPED.equals(documentoOriginal.getTypeFormatSign())) { Node signatureNode = (Node) request.getSession().getAttribute(Constantes.SIGNATURE_PRINCIPAL); request.getSession().removeAttribute(Constantes.SIGNATURE_PRINCIPAL); if (signatureNode != null) { // Busco el nodo sobre el que adjuntar el resto del contenido Element nscontext = XMLUtils.createDSctx(xmlDocument, "ds", Constants.SignatureSpecNS); try { Node nodo = XPathAPI.selectSingleNode(xmlDocument, "//ds:Signature", nscontext); // Aadimos todos los signatures pendientes al documento // original. Node xmlSignatureNode = xmlDocument.importNode(signatureNode, true); nodo.getParentNode().appendChild(xmlSignatureNode); } catch (TransformerException e) { log.warn("No se puede procesar la Signature previa.", e); } } } } }