Aceptación SSL autofirmada en Android

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

Una muestra de código sería perfecta.

He buscado en todas partes en Internet y, aunque algunas personas afirman haber encontrado la solución, o no funciona o no hay un código de muestra para respaldarla.

Tengo esta funcionalidad en exchangeIt, que se conecta al intercambio de Microsoft a través de WebDav. Aquí hay un código para crear un HttpClient que se conectará a certificados autofirmados a través de 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 el intercambio es de código abierto y alojado en googlecode aquí , si tiene algún problema. Ya no estoy trabajando activamente en eso, pero el código debería funcionar.

Tenga en cuenta que desde Android 2.2 el proceso ha cambiado un poco, así que verifique 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 par esté autenticado. Ver RFC 2246”.

Habiendo dicho eso, aquí hay otra forma, sin ninguna clase adicional:

 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 compañía a HTTPS, pero usando certificados SSL autofirmados.

He buscado en todas partes, pero todas las respuestas marcadas “correctas” que he encontrado consisten en deshabilitar la validación de certificados, anulando claramente todo el sentido de SSL.

Finalmente llegué a una solución:

  1. Crear Local KeyStore

    Para permitir que su aplicación valide sus certificados autofirmados, debe proporcionar un almacén de claves personalizado con los certificados de manera que Android pueda confiar en su endpoint.

El formato para dichos almacenes de claves personalizados es “BKS” de BouncyCastle , por lo que necesita la versión 1.46 de BouncyCastleProvider que puede descargar aquí .

También necesita su certificado autofirmado, asumiré que se llama self_cert.pem .

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

  $ 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 apunta a un archivo donde se creará su almacén de claves. NO DEBE EXISTIR .

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

STOREPASS es su contraseña del keystore recién creada.

  1. Incluye KeyStore en tu aplicación

Copie su keystore recién creado de PATH_TO_KEYSTORE a res/raw/certs.bks ( certs.bks es solo el nombre del archivo; puede usar el nombre que desee)

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

   ... *STOREPASS* ...  
  1. Crea una clase que herede 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 use una instancia de **MyHttpClient** como lo haría con **DefaultHttpClient** para hacer sus consultas HTTPS, y usará y validará correctamente sus 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 hacer más comprobaciones para asegurarse de que los certificados son los que está esperando, entonces cualquiera puede crear un certificado autofirmado y puede pretender ser su servidor. En ese punto, no tienes seguridad real.

La única manera legítima de hacerlo (sin escribir una stack SSL completa) es agregar un ancla confiable adicional para que sea confiable durante el proceso de verificación del certificado. Ambos implican codificar el certificado de anclaje de confianza en su aplicación y agregarlo a los anclajes de confianza que el sistema operativo proporciona (de lo contrario, no podrá conectarse a su sitio si obtiene un certificado real).

Soy consciente de dos maneras de hacer esto:

  1. Cree una tienda de confianza personalizada 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 contenga 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 no se ha probado por completo y es posible que ni siquiera compile, pero al menos debería orientarlo en la dirección correcta.

La respuesta de Brian Yarger también funciona en Android 2.2 si modificas la sobrecarga del método createSocket más grande de la siguiente manera. Me llevó un tiempo lograr que los SSL autofirmados funcionaran.

 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 se supone que su enfoque funciona cuando se usa una vista web. No lo consigo en Android 2.3. En cambio, me aparece una pantalla en blanco.

Después de buscar un poco más, me encontré con esta solución simple para manejar los errores de SSL en una vista web que funcionó como un encanto para mí.

En el controlador, compruebo si estoy en un modo dev especial y llamo a handler.proceed (); de lo contrario, llamo a handler.cancel (). Esto me permite hacer desarrollo contra un certificado autofirmado en un sitio web local.