Para poder utilizar un servidor a través de HTPPS, 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 del dispositivo.
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 artículo vamos a aplicar la tercera solución tomando como ejemplo el código de la documentación oficial. Los pasos son:
- Obtener el certificado del servidor en formato X.509. Eso podemos hacerlo con firefox:
- Paso 1: Darle clic al candado verde y luego en la fecha «>» para luego hacer clic en la opción «Más información».
- Paso 2: Darle clic a la opción Ver certificado:
- Paso 3: Darle clic a exportar certificado y luego guardar en el formato «X.509 (PEM)»:
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 es «certificado.crt» por el nombre real del certificado que van a utilizar:
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 java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import android.content.Context; import android.util.Log; public class CustomSSLSocketFactory { private CustomSSLSocketFactory() { super(); } private static SSLSocketFactory sslSocketFactory; public static SSLSocketFactory getSSLSocketFactory(Context context) throws CertificateException, IOException, GeneralSecurityException { // Load CAs from an InputStream // (could be from a resource or ByteArrayInputStream or ...) CertificateFactory cf = CertificateFactory.getInstance("X.509"); InputStream caInput = new BufferedInputStream(context.getAssets().open("certificado.crt")); Certificate ca; try { ca = cf.generateCertificate(caInput); Log.d("SSL","ca=" + ((X509Certificate) ca).getSubjectDN()); } finally { caInput.close(); } // Create a KeyStore containing our trusted CAs 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:
URL url =
new
URL(
"https://www.dominio.com"
);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod(
"GET"
);
connection.setSSLSocketFactory(CustomSSLSocketFactory.getSSLSocketFactory(context));