org.viafirma.nucleo.firma.imp.FirmaOpenocesAppletBridgeImp.java Source code

Java tutorial

Introduction

Here is the source code for org.viafirma.nucleo.firma.imp.FirmaOpenocesAppletBridgeImp.java

Source

/* 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);
                }
            }
        }
    }
}