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.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; } } }