28/06/2015
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:
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:
- Obtener el certificado del servidor en formato X.509. La forma de hacerlo con Firefox está descrita en este artículo.
- Incluir el certificado en la app. Simplemente copiamos el fichero obtenido en el paso anterior en directorio assets.
- 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; } }
- 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.ejemplo.com"); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setSSLSocketFactory(CustomSSLSocketFactory.getSSLSocketFactory(context)); ...
Hola amigo buenas tardes, Tengo una duda, hago una nueva clase llamada CustomSSLSocketFactory pero al colocar el:
URL url = new URL(«https://www.ejemplo.com»);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod(«GET»);
connection.setSSLSocketFactory(CustomSSLSocketFactory.getSSLSocketFactory(context));
en mi aplicacion justamente debajo de mi metodo login:
public LoginTask(Login activity, LoginTaskFinishedListener finishedListener) throws IOException {
this.activity = activity;
this.finishedListener = finishedListener;
dialog = new ProgressDialog(activity);
SSLContext context = new SSLContext();
URL url = new URL(«https://carloso.org:8000/AltaMovil»);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod(«GET»);
connection.setSSLSocketFactory(CustomSSLSocketFactory.getSSLSocketFactory(context))
}
me marca un ERRR en Context, espero puedas ayudarme. saludos
Hola quizá ya sea algo tarde para comentar, pero usa getaApplicationContext(); o mi_clase.this