Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


Aceptación de SSL auto-firmada en Android

¿Cómo acepto un certificado autofirmado en Java en Android?

Una muestra de código sería perfecta.

He buscado por todas partes en Internet y mientras que algunas personas dicen haber encontrado la solución, o no funciona o no hay código de ejemplo para respaldarlo.

  • Javax.net.ssl.SSLHandshakeException: Conexión cerrada por peer en com.android.org.conscrypt.NativeCrypto.SSL_do_handshake (Native Method)
  • El proxy Charles falla en el método de conexión SSL
  • Recibir y validar certificado del servidor HTTPS - android
  • Android / Java - ¿Cómo crear una conexión HTTPS?
  • ¿Cómo enviar solicitudes de proxy a api.twitter.com incluyendo certificados SSL?
  • Javax.net.ssl.sslpeerunverifiedexception sin certificado de igualdad
  • ¿Cómo instalar el certificado de CA de confianza en un dispositivo Android?
  • Android: streaming de video por servidor HTTPS local: certificado SSL rechazado
  • 7 Solutions collect form web for “Aceptación de SSL auto-firmada en Android”

    Tengo esta funcionalidad en exchangeIt, que se conecta a Microsoft Exchange a través de WebDav. Aquí hay un código para crear un HttpClient que se conectará a cert firmado por SSL:

    SchemeRegistry schemeRegistry = new SchemeRegistry(); // http scheme schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // https scheme schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443)); HttpParams params = new BasicHttpParams(); params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30); params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30)); params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); 

    El EasySSLSocketFactory está aquí , y el EasyX509TrustManager está aquí .

    El código para exchangeIt es de código abierto, y alojado en googlecode aquí , si tiene algún problema. No estoy trabajando activamente en ello más, pero el código debería funcionar.

    Ten en cuenta que desde Android 2.2 el proceso ha cambiado un poco, así que comprueba esto para que el código anterior funcione.

    Como EJP comentó correctamente, "Los lectores deben tener en cuenta que esta técnica es radicalmente insegura SSL no es seguro a menos que al menos un peer sea autenticado Ver RFC 2246."

    Dicho esto, aquí hay otra manera, sin ninguna clase extra:

     import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager; private void trustEveryone() { try { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){ public boolean verify(String hostname, SSLSession session) { return true; }}); SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new X509TrustManager[]{new X509TrustManager(){ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }}}, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory( context.getSocketFactory()); } catch (Exception e) { // should never happen e.printStackTrace(); } } 

    Me enfrenté a este problema ayer, mientras migraba la API RESTful de nuestra empresa a HTTPS, pero utilizando certificados SSL auto-firmados.

    He buscado por todas partes, pero todas las respuestas "correctas" marcadas que he encontrado consistían en deshabilitar la validación de certificados, anulando claramente todo el sentido de SSL.

    Finalmente llegué a una solución:

    1. Crear KeyStore local

      Para permitir que la aplicación valide sus certificados autofirmados, debe proporcionar a los almacenes de claves personalizados los certificados de forma que Android pueda confiar en su punto final.

    El formato para tales almacenes de llaves personalizados es "BKS" de BouncyCastle , por lo que necesita la versión 1.46 de BouncyCastleProvider que puede descargar aquí .

    También necesitas tu certificado autofirmado, asumiré que se llama self_cert.pem .

    Ahora el comando para crear su almacén de claves es:

     <!-- language: lang-sh --> $ keytool -import -v -trustcacerts -alias 0 \ -file *PATH_TO_SELF_CERT.PEM* \ -keystore *PATH_TO_KEYSTORE* \ -storetype BKS \ -provider org.bouncycastle.jce.provider.BouncyCastleProvider \ -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \ -storepass *STOREPASS* 

    PATH_TO_KEYSTORE señala un archivo en el que se creará su almacén de claves. NO DEBE EXISTIR .

    PATH_TO_bcprov-jdk15on-146.jar.JAR es la ruta de acceso a la PATH_TO_bcprov-jdk15on-146.jar.JAR .jar descargada.

    STOREPASS es la contraseña de keystore recién creada.

    1. Incluya KeyStore en su aplicación

    Copie su almacén de claves recién creado de PATH_TO_KEYSTORE a res/raw/certs.bks ( certs.bks es sólo el nombre del archivo, puede usar cualquier nombre que desee)

    Crear una clave en res/values/strings.xml con

     <!-- language: lang-xml --> <resources> ... <string name="store_pass">*STOREPASS*</string> ... </resources> 
    1. Cree una clase que hereda DefaultHttpClient

       import android.content.Context; import android.util.Log; 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.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } } 

    Ahora simplemente usa una instancia de **MyHttpClient** como lo harías con **DefaultHttpClient** para realizar tus consultas HTTPS, y usará y validará correctamente tus certificados SSL autofirmados.

     HttpResponse httpResponse; HttpPost httpQuery = new HttpPost("https://yourserver.com"); ... set up your query ... MyHttpClient myClient = new MyHttpClient(myContext); try{ httpResponse = myClient.(peticionHttp); // Check for 200 OK code if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) { ... do whatever you want with your response ... } }catch (Exception ex){ Log.d("httpError", ex.getMessage()); } 

    A menos que me haya perdido algo, las otras respuestas en esta página son PELIGROSAS, y son funcionalmente equivalentes a no usar SSL en absoluto. Si confía en los certificados autofirmados sin realizar comprobaciones adicionales para asegurarse de que los certificados son los que está esperando, cualquiera puede crear un certificado autofirmado y puede pretender ser su servidor. En ese momento, usted no tiene seguridad real.

    La única forma legítima de hacerlo (sin escribir una pila de SSL completa) es agregar un ancla de confianza adicional para que se pueda confiar durante el proceso de verificación de certificados. Ambos implican la codificación en disco del certificado de anclaje de confianza en su aplicación y su adición a cualquier ancla de confianza que el sistema operativo proporcione (o bien no podrá conectarse a su sitio si obtiene un certificado real).

    Soy consciente de dos maneras de hacer esto:

    1. Cree un almacén de confianza personalizado como se describe en http://www.ibm.com/developerworks/java/library/j-customssl/#8

    2. Cree una instancia personalizada de X509TrustManager y anule el método getAcceptedIssuers para devolver una matriz que contiene su certificado:

       public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; } 

    Tenga en cuenta que este código es completamente no probado y no puede incluso compilar, pero al menos debe guiar en la dirección correcta.

    La respuesta de Brian Yarger funciona también en Android 2.2 si modifica la sobrecarga del método createSocket más grande de la siguiente manera. Me llevó un tiempo conseguir SSLs auto-firmados trabajando.

     public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose); } 

    En Android, HttpProtocolParams acepta ProtocolVersion lugar de HttpVersion .

     ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1); HttpProtocolParams.setVersion(params, pv); 

    @Chris – Publicar esto como una respuesta ya que no puedo agregar comentarios (todavía). Me pregunto si su enfoque se supone que funciona cuando se utiliza un webView. No puedo conseguirlo hacerlo en Android 2.3 – en su lugar simplemente obtener una pantalla en blanco.

    Después de un poco más de búsqueda, me encontré con esta solución simple para la gestión de errores SSL en un webView que funcionó como un encanto para mí.

    En el manejador compruebo para ver si estoy en un modo dev especial y llamo handler.proceed (), de lo contrario llamo handler.cancel (). Esto me permite hacer el desarrollo en contra de un CERT auto-firmado en un sitio web local.

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.