org.viafirma.nucleo.validacion.CRLUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.viafirma.nucleo.validacion.CRLUtil.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.validacion;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.NoSuchProviderException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.viafirma.nucleo.X509.X509Handler;
import org.viafirma.util.Constantes;

/**
 * Se encarga de gestionar la recuperacin de CRLs de los certificados.
 * Implementa los diferentes tipos de recuperacin de certificados: <br>
 * 1.- CRL en LDAP
 * 2.- CRLS usando el punto de distribucin indicado en el OID 2.5.29.31.
 * 
 * @author Felix Garcia Borrego (borrego at gmail.com)
 * @author Alexis Castilla Armro (Pencerval at gmail.com)
 */
public class CRLUtil {
    private static Log log = LogFactory.getLog(CRLUtil.class);

    /**
     * Identificador del OID en el que de forma estandar se almacenan los puntos
     * de distribucin de las CRLs.
     */
    public final String OID_CRLS = "2.5.29.31";

    /**
     * Nombre del campo en el que se almacena el CN indicando la crl que se esta
     * utilizando. EJ CN=CRL1969
     */
    public static final String FNMT_CN_IDENTIFICADOR = "2.5.4.3";

    /**
     * Host de conexin para la validacin de las crls de la FNMT
     */
    public String fnmtLDAPHostURL;

    /**
     * Usuario de acceso al LDAP de la FNMT
     */
    private String fnmtPrincipal;

    /**
     * Credenciales de acceso al LDAP de la FNMT
     */
    private String fnmtCredencial;

    /**
     * Recupera todas las crls que tiene publicadas dentro del cerficicado.
     * <p>
     * Si el certificado es ancert el metodo obtiene las urls desde las que
     * descargarse las crls y se las descarga.
     * <p>
     * Si el certificado es FNMT recupera la ruta dentro del ldap desde la que
     * descargarse las crls.
     * <p>
     * Si el certificado es de Viavansi se conecta a las crls y se las descarga.
     * TODO: En el futuro cachear las crls.
     * 
     * @param certificadoX509
     * @return
     * @throws CRLException
     * @throws NoSuchProviderException
     * @throws CertificateException
     */
    public List<X509CRL> getCRLs(X509Certificate certificadoX509)
            throws CRLException, CertificateException, NoSuchProviderException {

        // Intentamos recuperar las crls del certificado desde la cache, para
        // minimizar los acccesos.
        List<X509CRL> listCrlFromCache = CrlCache.getInstance().getCrlsFrom(certificadoX509);
        if (listCrlFromCache == null) {
            // Es necesaria la recuperacin de las CRLs
            List<X509CRL> listCRLs = new ArrayList<X509CRL>(0);
            // ********************************************************************************
            // si es un certiticado de la FNMT hay que acceder al ldap para
            // recuperar las crls.
            // TODO Separar la obtencin de CRLS creando Handler especializados
            // TODO Mejorar el control de errores
            if (certificadoX509.getIssuerDN().getName().contains(Constantes.FNMT_ISSUERDN)) {
                // es un certificado de la FNMT. el procesamiento es diferente
                // al
                // resto, es atacando a un LDAP o a un OCSP en funcion de la configuracion del server
                // Comprobamos que no tiene valores nulos.
                if (!isSomeFNMTValorNull()) {
                    log.debug("El certificado es de la fbrica, lo validamos utilizando el LDAP");
                    listCRLs = getCrlLDAPFNMT(certificadoX509);
                }
            } else {
                // Es un certificado de ANCERT , Camerfirma o de VIAVANSI con el punto de
                // distribucin correctamente indicado.
                listCRLs = getCrlsPuntoDistribucion(certificadoX509);
            }
            // Aadimos a Cache
            CrlCache.getInstance().addToCache(certificadoX509, listCRLs);

            return listCRLs;
        } else {
            log.debug("Existe una cache vlida de crls asociada al certificado actual.");
            return listCrlFromCache;
        }
    }

    /**
     * Retorna el listado de CRLs para los certificados que tienen informacin
     * correcta sobre sus puntos de distrubicin. 1.- Recupera las urls de los
     * puntos de distribucin de crls. 2.- Se descarga todas las crls.
     * 
     * @param certificadoX509
     * @return
     * @throws NoSuchProviderException
     * @throws CertificateException
     */
    private List<X509CRL> getCrlsPuntoDistribucion(X509Certificate certificadoX509)
            throws CertificateException, NoSuchProviderException {
        CertificateFactory factoriaCertificados = CertificateFactory.getInstance("X.509",
                BouncyCastleProvider.PROVIDER_NAME);
        List<String> urls = null;
        // recuperos los puntos de distribucin definidos del certificado.
        urls = getCrlPuntosDeDistribucion(certificadoX509);
        List<X509CRL> crls = new LinkedList<X509CRL>();
        if (urls != null) {
            // itero sobre las urls para ir obteniendo los listados
            for (String hostURL : urls) {
                log.debug("url ->" + hostURL);
                try {
                    if (hostURL == null) {
                        log.debug("La url de la crl no es correcta.");

                    } else if (!hostURL.startsWith("http:")) {
                        log.debug("La url de la crl no es correcta. " + hostURL);
                    } else {
                        InputStream ioCrl = getIoCrlFromUrl(hostURL);

                        // leo el io para generar un fichero de crl
                        X509CRL crl = (X509CRL) factoriaCertificados.generateCRL(ioCrl);
                        if (crl != null) {
                            crls.add(crl);
                            // log.debug("CRLer -->" + crl.get());
                            log.debug("Effective   From -->" + crl.getThisUpdate());
                            log.debug("Nextate    -->" + crl.getNextUpdate());
                        } else {
                            log.debug("No se puede recuperar o no es un cert valido " + hostURL);
                        }
                        try {
                            ioCrl.close();
                        } catch (Exception e) {
                            // No se ha podido cerrar la conexin con la crl, sin importancia.
                        } // no importa si no podemos cerrar la conexin(
                          // significa que ya esta cerrada)
                    }
                } catch (CRLException e) {
                    log.warn(
                            "no se ha podido conectar a host para descargar las crls, en este momento no estan disponibles."
                                    + e.getMessage(),
                            e);
                    // e.printStackTrace();
                } catch (Exception e) {
                    log.warn(
                            "no se ha podido conectar a host para descargar las crls, en este momento no estan disponibles."
                                    + e.getMessage(),
                            e);
                    e.printStackTrace();
                }
            }
        }
        return crls;
    }

    /**
     * Recupera el listado de Crls obtenidas desde el LDAP. 
     * TODO: Separar cada implementacin en un IMPL concreto que tenga que cumplir con una interfaz
     * para resolver las crls y para parsear el certificado 
     * NOTA: para utilizar de forma oficial la validazin de CRLs de la FNMT es necesario firmar un convenio.
     * 
     * @param certificadoX509
     * @return
     */
    private List<X509CRL> getCrlLDAPFNMT(X509Certificate certificadoX509) {
        List<X509CRL> crls = new LinkedList<X509CRL>();
        // ********************************************************************************
        // si es un certiticado de la FNMT hay que acceder al ldap para
        // recuperar las crls.
        try {
            CertificateFactory factoriaCertificados = CertificateFactory.getInstance("X.509",
                    BouncyCastleProvider.PROVIDER_NAME);
            // es un certificado de la FNMT. el procesamiento es diferente
            // al resto, es atacando a un LDAP
            // recuperamos del LDAP el certificado
            // NOTA: Esta url es solo para pruebas, para utilizar de forma
            // oficial la validazin de CRLs de la FNMT es necesario firmar un
            // convenio
            // ldap-2.cert.fnmt.es:389
            InputStream ioCrl = getIoCrlFromFNMTLDAP(certificadoX509);
            if (ioCrl != null) {
                // la crl del fichero actual esta publicada, recuperamos la crl
                // leo el io para generar un fichero de crl
                System.out.println("***ioCrl:" + ioCrl);
                X509CRL crl = (X509CRL) factoriaCertificados.generateCRL(ioCrl);
                System.out.println("***Despues deioCrl:" + crl);
                try {
                    if (crl != null) {
                        crls.add(crl);
                        System.out.println("***3:" + crl.getIssuerDN());
                        log.debug("CRLer     -->" + crl.getIssuerDN());
                        log.debug("Effective   From -->" + crl.getThisUpdate());
                        log.debug("Nextate    -->" + crl.getNextUpdate());
                        crls.add(crl);
                    } else {
                        log.debug("No se puede recuperar o no es un cert valido .");
                    }

                    ioCrl.close();
                } catch (Throwable e) {
                    log.warn("Problemas al recuperar la crl ." + e.getMessage());
                    e.printStackTrace();
                } // no importa si no podemos cerrar la conexin( significa
                  // que ya esta cerrada)
            } else {
                log.error("No se ha recuperado la crl.");
            }
        } catch (CRLException e) {
            log.warn("No se puede recuperar la crl." + e.getMessage());
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return crls;
    }

    /**
     * Se conecta a la url indicada y se descarga las crls. No se esta usando
     * 
     * @param hostURL
     * @return
     * @throws CRLException
     *             No se ha podido recuperar el listado
     */
    public InputStream getIoCrlFromUrl(String hostURL) throws CRLException {
        URL url;
        try {
            url = new URL(hostURL);
            return url.openStream();
        } catch (MalformedURLException e) {
            throw new CRLException("La url  host: " + hostURL + " esta mal formada", e);
        } catch (IOException e) {
            throw new CRLException(
                    "No se ha podido conectar al host: " + hostURL + " para recuperar el listado de CRLs", e);
        }

    }

    /**
     * Se conecta a la url indicada y se descarga las crls. No se esta usando
     * *******************!!! En desarrollo, no funciona
     * 
     * @param hostURL
     * @return
     * @throws CRLException
     *             No se ha podido recuperar el listado
     * @throws CertificateParsingException
     */
    @SuppressWarnings("unchecked")
    private InputStream getIoCrlFromFNMTLDAP(X509Certificate certificadoX509)
            throws CRLException, CertificateParsingException {
        // ************************
        // recupero las propiedades para realizar la busqueda en LDAP.
        // EJ :[CN=CRL1, OU=FNMT Clase 2 CA, O=FNMT, C=ES] {2.5.4.11=FNMT Clase
        // 2 CA, 2.5.4.10=FNMT, 2.5.4.6=ES, 2.5.4.3=CRL1}
        Map<String, String> propiedades = new HashMap<String, String>();
        try {
            log.debug("Recuperando puntos de distribucin CRL del certificado FNMT: "
                    + certificadoX509.getIssuerDN());
            // recupero la extensin OID 2.5.29.31 ( id-ce-cRLDistributionPoinds
            // segun el RFC 3280 seccin 4.2.1.14)
            byte[] val1 = certificadoX509.getExtensionValue(OID_CRLS);
            if (val1 == null) {
                log.debug("   El certificado NO tiene punto de distribucin de CRL ");
            } else {
                ASN1InputStream oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(val1));
                DERObject derObj = oAsnInStream.readObject();
                DEROctetString dos = (DEROctetString) derObj;
                byte[] val2 = dos.getOctets();
                ASN1InputStream oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(val2));
                DERObject derObj2 = oAsnInStream2.readObject();

                X509Handler.getCurrentInstance().readPropiedadesOid(OID_CRLS, derObj2, propiedades);

            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CertificateParsingException(e.toString());
        }

        // comprobamos la configuracin
        if (isSomeFNMTValorNull()) {
            throw new CRLException(
                    "Para el acceso a las CRLs de la FNMT es necesario las credenciales. Indique el parametro de configuracin :"
                            + Constantes.CONEXION_LDAP_CRL_FNMT);
        }

        String CN = "CN=" + propiedades.get(FNMT_CN_IDENTIFICADOR) + "," + certificadoX509.getIssuerDN();
        log.debug("Buscando en el LDAP " + CN);

        // **********************************************
        // Nos conectamos al LDAP para recuperar la CRLs.

        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, fnmtLDAPHostURL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, fnmtPrincipal);
        env.put(Context.SECURITY_CREDENTIALS, fnmtCredencial);
        env.put(Context.REFERRAL, "follow");

        try {
            DirContext ctx = new InitialDirContext(env);
            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration namings = (ctx.search(CN, "(objectclass=*)", searchControls));

            log.debug("Se ha logrado conectar al LDAP");

            if (namings.hasMore()) {
                log.debug("Recuperando el contenido de la CRLs");
                // recupero el resultado
                SearchResult resultado = ((SearchResult) namings.next());

                // recupero todos los atributos del resultado
                Attributes avals = resultado.getAttributes();

                // recupero los bytes.
                byte[] bytes;
                if ((avals.get("certificateRevocationList;binary")) != null) {
                    log.debug("Atributos deben estar en binario");
                    Attribute atributo = (avals.get("certificateRevocationList;binary"));
                    bytes = ((byte[]) atributo.get());
                } else {
                    log.debug("Atributos en exadecimal En Hexadecimal");
                    Attribute atributo = (avals.get("certificateRevocationList"));
                    bytes = ((byte[]) atributo.get());
                    log.debug("Por implementar");
                }

                if (bytes != null) {
                    ByteArrayInputStream io = new ByteArrayInputStream(bytes);
                    return io;
                }
            }
        } catch (NamingException e) {
            log.error("No se puede conectar al LDAP!!", e);
        }
        return null;
    }

    /**
     * Recupero los puntos de distribucin
     * 
     * @param certificadoX509
     * @return
     */
    private List<String> getCrlPuntosDeDistribucion(X509Certificate certificadoX509)
            throws CertificateParsingException {
        try {
            log.debug("Recuperando puntos de distribucin CRL del certificado: " + certificadoX509.getSubjectDN());
            // recupero la extensin OID 2.5.29.31 ( id-ce-cRLDistributionPoinds
            // segun el RFC 3280 seccin 4.2.1.14)

            byte[] val1 = certificadoX509.getExtensionValue(OID_CRLS);
            if (val1 == null) {
                if (certificadoX509.getSubjectDN().getName().equals(certificadoX509.getIssuerDN().getName())) {
                    log.debug("El certificado es un certificado raiz: " + certificadoX509.getSubjectDN().getName());
                } else {
                    log.warn("   El certificado NO tiene punto de distribucin de CRL : "
                            + certificadoX509.getSubjectDN().getName());
                }
                return Collections.emptyList();
            } else {
                ASN1InputStream oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(val1));
                DERObject derObj = oAsnInStream.readObject();
                DEROctetString dos = (DEROctetString) derObj;
                byte[] val2 = dos.getOctets();
                ASN1InputStream oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(val2));
                DERObject derObj2 = oAsnInStream2.readObject();
                // Map<String,String> propiedades= new HashMap<String,String>();
                List<String> urls = getDERValue(derObj2);
                return urls;
                /*
                 * CertificadoHelper.getCurrentInstance().readPropiedadesOid(OID_CRLS,derObj2,propiedades);
                 * if(log.isDebugEnabled())log.debug("Informacin sobre CRls del
                 * certificado que ha sido recuperada: "+propiedades); // por
                 * simplificar, aunque el certificado informe de varias crls que
                 * utilizar. Solo trabajamos con la primera List listaCrls=new
                 * ArrayList(1); listaCrls.add(propiedades.get(OID_CRLS));
                 * return listaCrls;//listaCrls.addAll(getDERValue(derObj2))
                 */}
        } catch (Exception e) {
            e.printStackTrace();
            throw new CertificateParsingException(e.toString());
        }
    }

    /**
     * Parsea el objeto y devuelve un listado con las urls de punto de
     * distribucin de las CRLs
     * 
     * @param derObj
     * @return
     */
    @SuppressWarnings("unchecked")
    private List<String> getDERValue(DERObject derObj) {
        if (derObj instanceof DERSequence) {
            List<String> list = new LinkedList<String>();
            DERSequence seq = (DERSequence) derObj;
            Enumeration enumeracion = seq.getObjects();
            while (enumeracion.hasMoreElements()) {
                DERObject nestedObj = (DERObject) enumeracion.nextElement();
                List<String> appo = getDERValue(nestedObj);
                if (appo != null) {
                    list.addAll(appo);
                }
            }
            return list;
        } else if (derObj instanceof DERTaggedObject) {
            DERTaggedObject derTag = (DERTaggedObject) derObj;
            if ((derTag.isExplicit() && !derTag.isEmpty()) || derTag.getObject() instanceof DERSequence) {
                DERObject nestedObj = derTag.getObject();
                List<String> ret = getDERValue(nestedObj);
                return ret;
            } else {
                DEROctetString derOct = (DEROctetString) derTag.getObject();
                String val = new String(derOct.getOctets());
                List<String> ret = new LinkedList<String>();
                ret.add(val);
                return ret;
            }
        } else if (derObj instanceof DERSet) {
            Enumeration enumSet = ((DERSet) derObj).getObjects();
            List<String> list = new LinkedList<String>();
            while (enumSet.hasMoreElements()) {
                DERObject nestedObj = (DERObject) enumSet.nextElement();
                List<String> appo = getDERValue(nestedObj);
                if (appo != null) {
                    list.addAll(appo);
                }
            }
            return list;
        } else if (derObj instanceof DERObjectIdentifier) {
            DERObjectIdentifier derId = (DERObjectIdentifier) derObj;
            List<String> list = new LinkedList<String>();
            list.add(derId.getId());
            return list;
        } else if (derObj instanceof DERPrintableString) {
            // hemos localizado un par id-valor
            String valor = ((DERPrintableString) derObj).getString();
            List<String> list = new LinkedList<String>();
            list.add(valor);
            return list;
        } else {
            log.fatal("tipo de dato en ASN1 al recuperar las crls no es reconocido : " + derObj);
        }
        return null;
    }

    private static CRLUtil singleton;

    public static CRLUtil getCurrentInstance() {
        if (singleton == null) {
            singleton = new CRLUtil();
        }
        return singleton;
    }

    private CRLUtil() {
        super();
    }

    /**
     * Inicializa la configuracin necesaria para el acceso remoto a CRLs.
     * 
     * @param properties
     */
    public static void init(Properties properties) {
        String configuracionFNMT = properties.getProperty(Constantes.CONEXION_LDAP_CRL_FNMT);

        if (configuracionFNMT == null) {
            log.info(
                    "No se ha indicado configuracin para el acceso a las crls de FNMT. Esta funcionalidad no estara disponible");
        } else {
            try {
                CRLUtil singleton = getCurrentInstance();
                String[] datos = configuracionFNMT.split(";");
                singleton.fnmtLDAPHostURL = datos[0];
                singleton.fnmtPrincipal = datos[1];
                singleton.fnmtCredencial = datos[2];
            } catch (Exception e) {
                log.error(
                        "La configturacin para la conexin a la FNMT no tiene el formato correcto. El formato esperado es host;principal;credencial.");
            }
        }
    }

    private boolean isSomeFNMTValorNull() throws CRLException {
        if (fnmtLDAPHostURL == null || fnmtPrincipal == null || fnmtCredencial == null) {
            //throw new CRLException("Para el acceso a las CRLs de la FNMT es necesario las credenciales. Indique el parametro de configuracin :" + Constantes.CONEXION_LDAP_CRL_FNMT);
            return true;
        } else {
            return false;
        }
    }

}