Tip Android #35: HTTPS y certificados

28/06/2015
android tip

Para poder interactuar con un servidor a través de HTPPS, utilizando por ejemplo el código presentado en el tip #08, el certificado digital SSL del servidor debe ser reconocido como un certificado “de confianza” por Android. Podemos comprobar los certificados reconocidos en una instalación de Android en las opciones de seguridad:

android trusted CA certificates

Si intentamos conectarnos a un servidor mediante https y Android no reconoce el certificado como confiable obtendremos la siguiente excepción:

 javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Para solucionar este problema y realizar la conexión tenemos tres alternativas:

  • Instalar “manualmente” el certificado en Android. Obligar al usuario a realizar esta instalación no parece una buena idea.
  • Aceptar cualquier certificado. Es la solución más simple pero supone un problema de seguridad ya que no podemos asegurar la identidad del servidor.
  • Incluir el certificado en la app y utilizarlo para validar el certificado del servidor.

En este tip vamos a aplicar la tercera solución tomando como ejemplo el código de la documentación oficial. Los pasos son:

  1. Obtener el certificado del servidor en formato X.509. La forma de hacerlo con Firefox está descrita en este artículo.
  2. Incluir el certificado en la app. Simplemente copiamos el fichero obtenido en el paso anterior en directorio assets.
  3. Crear un SSLSocketFactory que nos permita confiar en el certificado (podemos incluir todos los certificados que sean necesarios). La siguiente clase genérica está lista para ser usada fácilmente.

    package com.danielme.android.utils;
    
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.GeneralSecurityException;
    import java.security.KeyStore;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManagerFactory;
    
    import android.content.Context;
    
    /**
     * Devuelve un SSLSocketFactory que acepta el certificado ubicado en assets/FICHERO_CERT como
     * certificado de confianza 
     *
     */
    public class CustomSSLSocketFactory {
    
    	private CustomSSLSocketFactory() {
    		super();
    	}
    
    	private static SSLSocketFactory sslSocketFactory;
    
    	private static final String FICHERO_CERT = "certificado";
    
    	public static SSLSocketFactory getSSLSocketFactory(Context context)
    			throws CertificateException, IOException, GeneralSecurityException {
    		//sólo se instancia la primera vez que se necesite
    		if (sslSocketFactory == null) {
    			CertificateFactory cf = CertificateFactory.getInstance("X.509");
    			InputStream caInput = new BufferedInputStream(context.getAssets()
    					.open(FICHERO_CERT));
    			Certificate ca;
    			try {
    				ca = cf.generateCertificate(caInput);
    			} finally {
    				caInput.close();
    			}
    
    			//se añaden todos los certificados obtenidos desde el assets. En este caso
    			//sólo tenemos uno.
    			String keyStoreType = KeyStore.getDefaultType();
    			KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    			keyStore.load(null, null);
    			keyStore.setCertificateEntry("ca", ca);
    
    			// Create a TrustManager that trusts the CAs in our KeyStore
    			String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    			TrustManagerFactory tmf = TrustManagerFactory
    					.getInstance(tmfAlgorithm);
    			tmf.init(keyStore);
    
    			// Create an SSLContext that uses our TrustManager
    			SSLContext sslContext = SSLContext.getInstance("TLS");
    			sslContext.init(null, tmf.getTrustManagers(), null);
    
    			sslSocketFactory = sslContext.getSocketFactory();
    		}
    		return sslSocketFactory;
    	}
    }
    
    
  4. Aplicar el SSLContext generador por CustomSSLSocketFactory a las conexiones que lo requieran. Estas conexiones sólo aceptarán como sitios de confianza aquellos cuyos certificados hayan sido incluídos en el SSLSocketFactory
  5. URL url = new URL("https://www.ejemplo.com");
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    connection.setSSLSocketFactory(CustomSSLSocketFactory.getSSLSocketFactory(context));
    ...
    
<< TIPS ANDROID

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: