Aceptación de un certificado para HTTPs en Android

Estoy tratando de hacer conexiones Https en los teléfonos Android, utilizando HttpClient. El problema es que puesto que el certificado no está firmado sigo recibiendo "javax.net.ssl.SSLException: No certificado de servidor de confianza".

Ahora he visto un montón de soluciones donde simplemente acepta todos los certificados, pero ¿y si quiero preguntar al usuario? Quiero conseguir un diálogo similar al del navegador, dejando que el usuario decida continuar o no.

Preferentemente, me gustaría usar la misma certificación que el navegador. ¿Algunas ideas?

Lo primero que debe hacer es establecer el nivel de verificación. Tales niveles no son tanto:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

Aunque el método setHostnameVerifier () es obsoleto para la nueva biblioteca apache, pero para la versión en Android SDK es normal. Así que tomamos ALLOW_ALL_HOSTNAME_VERIFIER y lo configuramos en el método factory SSLSocketFactory.setHostnameVerifier() .

A continuación, debe configurar nuestra fábrica para el protocolo a https. Para ello, simplemente llame al método SchemeRegistry.register() .

Entonces usted necesita crear un DefaultHttpClient con SingleClientConnManager . También en el código de abajo puedes ver que por defecto también usaremos nuestro flag ( ALLOW_ALL_HOSTNAME_VERIFIER ) por el método HttpsURLConnection.setDefaultHostnameVerifier()

A continuación, el código funciona para mí:

 HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; DefaultHttpClient client = new DefaultHttpClient(); SchemeRegistry registry = new SchemeRegistry(); SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); registry.register(new Scheme("https", socketFactory, 443)); SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); // Set verifier HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); // Example send http request final String url = "https://encrypted.google.com/"; HttpPost httpPost = new HttpPost(url); HttpResponse response = httpClient.execute(httpPost); 

Los siguientes pasos principales son necesarios para lograr una conexión segura de las Autoridades de Certificación que no son consideradas como de confianza por la plataforma android.

Según lo solicitado por muchos usuarios, he reflejado las partes más importantes de mi artículo de blog aquí:

  1. Coge todos los certificados necesarios (root y cualquier CA intermedia)
  2. Cree un almacén de claves con keytool y el proveedor BouncyCastle e importe los certs
  3. Cargue el almacén de claves en su aplicación android y úselo para las conexiones seguras (recomiendo usar el Apache HttpClient en lugar del estándar java.net.ssl.HttpsURLConnection (más fácil de entender, más performante)

Coge los certs

Debe obtener todos los certificados que crean una cadena desde el certificado de punto final hasta la entidad emisora ​​de certificados raíz. Esto significa que cualquier CER de CA intermedio (si está presente) y también el certificado CA de raíz. No es necesario obtener el certificado de punto final.

Crear el almacén de claves

Descargue el Proveedor BouncyCastle y guárdelo en una ubicación conocida. Asegúrese también de que puede invocar el comando keytool (normalmente ubicado en la carpeta bin de su instalación JRE).

Ahora importa los certs obtenidos (no importa el cert del punto final) en un BouncyCastle formateado keystore.

No lo probé, pero creo que la orden de importar los certificados es importante. Esto significa que primero debe importar el certificado CA intermedio más bajo y luego hasta el certificado de la CA raíz.

Con el siguiente comando se creará un nuevo almacén de claves (si no está presente) con la contraseña mysecret y se importará el certificado de la CA intermedia. También definí el proveedor BouncyCastle, donde se puede encontrar en mi sistema de archivos y el formato keystore. Ejecute este comando para cada certificado de la cadena.

 keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret 

Compruebe si los certificados se importaron correctamente en el almacén de claves:

 keytool -list -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret 

Debe salir toda la cadena:

 RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93 IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43 

Ahora puede copiar el almacén de claves como recurso sin procesar en su aplicación android bajo res/raw/

Utilizar el almacén de claves en tu aplicación

En primer lugar tenemos que crear un cliente personalizado Apache HttpClient que utiliza nuestro keystore para conexiones HTTPS:

 public class MyHttpClient extends DefaultHttpClient { final Context context; public MyHttpClient(Context context) { this.context = context; } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // Register for port 443 our SSLSocketFactory with our keystore // to the ConnectionManager registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { // Get an instance of the Bouncy Castle KeyStore format KeyStore trusted = KeyStore.getInstance("BKS"); // Get the raw resource, which contains the keystore with // your trusted certificates (root and any intermediate certs) InputStream in = context.getResources().openRawResource(R.raw.mykeystore); try { // Initialize the keystore with the provided trusted certificates // Also provide the password of the keystore trusted.load(in, "mysecret".toCharArray()); } finally { in.close(); } // Pass the keystore to the SSLSocketFactory. The factory is responsible // for the verification of the server certificate. SSLSocketFactory sf = new SSLSocketFactory(trusted); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); return sf; } catch (Exception e) { throw new AssertionError(e); } } } 

Hemos creado nuestro HttpClient personalizado, ahora podemos usarlo para conexiones seguras. Por ejemplo, cuando realizamos una llamada GET a un recurso REST.

 // Instantiate the custom HttpClient DefaultHttpClient client = new MyHttpClient(getApplicationContext()); HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23"); // Execute the GET call and obtain the response HttpResponse getResponse = client.execute(get); HttpEntity responseEntity = getResponse.getEntity(); 

Eso es 😉

Si tiene un certificado personalizado / autofirmado en el servidor que no está en el dispositivo, puede utilizar la clase siguiente para cargarlo y usarlo en el lado del cliente en Android:

Coloque el archivo de certificado *.crt en /res/raw para que esté disponible en R.raw.*

Utilice debajo de la clase para obtener un HTTPClient o HttpsURLConnection que tendrá una fábrica de socket que utiliza ese certificado:

 package com.example.customssl; import android.content.Context; import org.apache.http.client.HttpClient; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; public class CustomCAHttpsProvider { /** * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority * certificate. * * @param context Application Context * @param certRawResId R.raw.id of certificate file (*.crt). Should be stored in /res/raw. * @param allowAllHosts If true then client will not check server against host names of certificate. * @return Http Client. * @throws Exception If there is an error initializing the client. */ public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception { // build key store with ca certificate KeyStore keyStore = buildKeyStore(context, certRawResId); // init ssl socket factory with key store SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore); // skip hostname security check if specified if (allowAllHosts) { sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier()); } // basic http params for client HttpParams params = new BasicHttpParams(); // normal scheme registry with our ssl socket factory for "https" SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", sslSocketFactory, 443)); // create connection manager ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); // create http client return new DefaultHttpClient(cm, params); } /** * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority * certificate. * * @param urlString remote url string. * @param context Application Context * @param certRawResId R.raw.id of certificate file (*.crt). Should be stored in /res/raw. * @param allowAllHosts If true then client will not check server against host names of certificate. * @return Http url connection. * @throws Exception If there is an error initializing the connection. */ public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId, boolean allowAllHosts) throws Exception { // build key store with ca certificate KeyStore keyStore = buildKeyStore(context, certRawResId); // 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); // Create a connection from url URL url = new URL(urlString); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.setSSLSocketFactory(sslContext.getSocketFactory()); // skip hostname security check if specified if (allowAllHosts) { urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier()); } return urlConnection; } private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { // init a default key store String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); // read and add certificate authority Certificate cert = readCert(context, certRawResId); keyStore.setCertificateEntry("ca", cert); return keyStore; } private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException { // read certificate resource InputStream caInput = context.getResources().openRawResource(certResourceId); Certificate ca; try { // generate a certificate CertificateFactory cf = CertificateFactory.getInstance("X.509"); ca = cf.generateCertificate(caInput); } finally { caInput.close(); } return ca; } } 

Puntos clave:

  1. Certificate objetos de Certificate se generan desde archivos .crt .
  2. Se KeyStore un KeyStore predeterminado.
  3. keyStore.setCertificateEntry("ca", cert) está agregando certificado al almacén de claves bajo alias "ca". Modifica el código para agregar más certificados (CA intermedio, etc.).
  4. El objetivo principal es generar un SSLSocketFactory que puede ser utilizado por HTTPClient o HttpsURLConnection .
  5. SSLSocketFactory se puede configurar más, por ejemplo, para omitir la verificación de nombre de host, etc.

Más información en: http://developer.android.com/training/articles/security-ssl.html

La respuesta superior no funcionó para mí. Después de alguna investigación encontré la información requerida en "Android Developer": https://developer.android.com/training/articles/security-ssl.html#SelfSigned

Crear una implementación vacía de X509TrustManager hizo el truco:

 private static class MyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } ... HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); try { // Create an SSLContext that uses our TrustManager SSLContext context = SSLContext.getInstance("TLS"); TrustManager[] tmlist = {new MyTrustManager()}; context.init(null, tmlist, null); conn.setSSLSocketFactory(context.getSocketFactory()); } catch (NoSuchAlgorithmException e) { throw new IOException(e); } catch (KeyManagementException e) { throw new IOException(e); } conn.setRequestMethod("GET"); int rcode = conn.getResponseCode(); 

Tenga en cuenta que esta implementación vacía de TustManager es sólo un ejemplo y su uso en un entorno productivo podría causar una amenaza de seguridad severa.

A continuación, le indicamos cómo puede agregar certificados adicionales a su KeyStore para evitar este problema: confiar en todos los certificados que utilizan HttpClient a través de HTTPS

No pedirá al usuario como lo pide, pero hará que sea menos probable que el usuario se ejecute en un error de "Certificado de servidor no confiable".

Me sentía frustrado al intentar conectar mi aplicación de Android a mi servicio RESTful mediante https. También estaba un poco molesto por todas las respuestas que sugerían desactivar la comprobación de certificados por completo. Si lo hace, ¿cuál es el punto de https?

Después de googled sobre el tema por un tiempo, finalmente encontré esta solución donde no se necesitan jarras externas, sólo las API de Android. Gracias a Andrew Smith, quien lo publicó en julio de 2014

  /** * Set up a connection to myservice.domain using HTTPS. An entire function * is needed to do this because myservice.domain has a self-signed certificate. * * The caller of the function would do something like: * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca"); * InputStream in = urlConnection.getInputStream(); * And read from that "in" as usual in Java * * Based on code from: * https://developer.android.com/training/articles/security-ssl.html#SelfSigned */ public static HttpsURLConnection setUpHttpsConnection(String urlString) { try { // Load CAs from an InputStream // (could be from a resource or ByteArrayInputStream or ...) CertificateFactory cf = CertificateFactory.getInstance("X.509"); // My CRT file that I put in the assets folder // I got this file by following these steps: // * Go to https://littlesvr.ca using Firefox // * Click the padlock/More/Security/View Certificate/Details/Export // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM)) // The MainActivity.context is declared as: // public static Context context; // And initialized in MainActivity.onCreate() as: // MainActivity.context = getApplicationContext(); InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt")); Certificate ca = cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); // 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 context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); // Tell the URLConnection to use a SocketFactory from our SSLContext URL url = new URL(urlString); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(context.getSocketFactory()); return urlConnection; } catch (Exception ex) { Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString()); return null; } } 

Funcionó bien para mi aplicación de mockup.

Google recomienda el uso de Android Volley para conexiones HTTP.

NUNCA NUKE certificados SSL (NUNCA).

Para nuke Certificados SSL es lo mismo que nada, con respecto a la seguridad.

He aquí un Gist que he creado, con un LoginApp básico, usando un certificado SSL Self-Signed sin nuking todos los certificados SSL, creando un TrustManager personalizado y usando Android Volley para realizar solicitudes HTTPS: https://gist.github.com / Ivanlmj / f11fb50d35fa1f2b9698bfb06aedcbcd

Aquí también hay otro Gist que puede ayudar, para la creación de Self-Signed SSL Certificates: https://gist.github.com/ivanlmj/a6a93dd142fb623d01262303d5bd8074

OBS .: Debe copiar el archivo .crt generado por el script anterior, al directorio "raw" de su proyecto de Android.

Pasé horas buscando soluciones para este problema, sin "nuking" los certificados SSL.

Los mejores saludos y hágamelo saber si usted tiene cualquier apuro.

Escribí la pequeña biblioteca ssl-utils-android para confiar en un certificado particular en Android.

Simplemente puede cargar cualquier certificado dando el nombre de archivo desde el directorio de activos.

Uso:

 OkHttpClient client = new OkHttpClient(); SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer"); client.setSslSocketFactory(sslContext.getSocketFactory()); 

Ninguno de estos arreglos funcionó para mi plataforma de desarrollo dirigida a SDK 16, versión 4.1.2, así que encontré una solución.

Mi aplicación almacena los datos en el servidor mediante " http://www.example.com/page.php?data=somedata "

Recientemente page.php se ha trasladado a " https://www.secure-example.com/page.php " y sigo recibiendo "javax.net.ssl.SSLException: No certificado de servidor de confianza".

En lugar de aceptar todos los certificados para una sola página, a partir de esta guía he resuelto mi problema escribiendo mi propia página.php publicado en " http://www.example.com/page.php "

 <?php caronte ("https://www.secure-example.com/page.php"); function caronte($url) { // build curl request $ch = curl_init(); foreach ($_POST as $a => $b) { $post[htmlentities($a)]=htmlentities($b); } curl_setopt($ch, CURLOPT_URL,$url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post)); // receive server response ... curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $server_output = curl_exec ($ch); curl_close ($ch); echo $server_output; } ?> 

Este es el problema resultante de la falta de SNI (Server Name Identification) de apoyo inA, ndroid 2.x. Yo estaba luchando con este problema durante una semana hasta que me encontré con la siguiente pregunta, que no sólo da un buen fondo del problema, sino también proporciona una solución eficaz y funcional carente de cualquier agujero de seguridad.

Error 'No peer certificate' en Android 2.3 pero NO en 4

La forma más sencilla de crear un certificado SSL

Abra Firefox (supongo que también es posible con Chrome, pero es más fácil para mí con FF)

Visite su sitio de desarrollo con un certificado SSL auto-firmado.

Haga clic en el certificado (junto al nombre del sitio)

Haga clic en "Más información"

Haga clic en "Ver certificado"

Haga clic en "Detalles"

Haga clic en "Exportar …"

Seleccione "Certificado X.509 con cadena (PEM)", seleccione la carpeta y el nombre para guardarlo y haga clic en "Guardar"

Vaya a la línea de comandos, al directorio donde descargó el archivo pem y ejecute "openssl x509 -inform PEM -outform DM -in .pem -out .crt"

Copie el archivo .crt en la raíz de la carpeta / sdcard dentro de su dispositivo Android Dentro de su dispositivo Android, Configuración> Seguridad> Instalar desde el almacenamiento.

Debe detectar el certificado y permitirle agregarlo al dispositivo. Vaya a su sitio de desarrollo.

La primera vez que le pida que confirme la excepción de seguridad. Eso es todo.

El certificado debe funcionar con cualquier navegador instalado en tu Android (navegador, Chrome, Opera, Dolphin …)

Recuerde que si usted está sirviendo sus archivos estáticos de un dominio diferente (todos somos píxeles de velocidad de página) también necesita agregar el certificado para ese dominio.

Tal vez esto servirá … funciona en clientes java que utilizan certificados autofirmados (no hay cheque del certificado). Tenga cuidado y úselo sólo para casos de desarrollo porque eso no es seguro en absoluto !!

Cómo ignorar los errores de certificados SSL en Apache HttpClient 4.0

Espero que funcione en Android sólo agregando HttpClient biblioteca … buena suerte!

  • ¿Cómo obtener InputStream de un https, Push Servlet fragmentado?
  • DownloadManager: entiende la política de reintentos y los códigos de error
  • Cómo utilizar el SSL con multi entidad en android
  • Android Habilitar TLSv1.2 en OKHttp
  • ¿Cómo puedo decir la versión TLS en Android Volley
  • ¿Cómo puedo implementar Pinning del certificado SSL mientras uso React Native
  • La conexión TLS que usa SSLSocket es lenta en el sistema operativo Android
  • Advertencia SSL de google play
  • Marcado del certificado SSL de MediaPlayer de Android
  • Aplicación de Android que trabaja en WIFI y 3G (Sin proxy), pero no funciona en 3G (Si proxy y puerto están asignados)
  • Error en la autenticación mutua en Android con javax.net.ssl.SSLHandshakeException: El apretón de manos falló
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.