Protocolos SSL / TLS y suites de cifrado con AndroidHttpClient

EDIT: disculpas si mi mensaje original fue mal redactado. Provocó cierta confusión, representada por comentarios al post original. Así que déjame intentarlo de nuevo:

Comencé con una pregunta. Quería resolver un problema en Android, pero no sabía cómo. Pasé mucho tiempo buscando soluciones en la red, pero no encontré ni una sola discusión sobre el tema en ninguna parte. Sin embargo, una serie de discusiones, incluidos los hilos de StackOverflow, me llevaron a una técnica que parecía prometedora. Resolví el problema. Pero la solución fue un poco complicada. Así que decidí publicar la pregunta aquí, pensando que a) debe haber una mejor solución, y espero que alguien sepa y publique la respuesta aquí; o b) quizás esta sea una buena solución, y como no encontré ninguna discusión sobre el tema en ninguna otra parte de la red, tal vez mi solución al problema sea útil para otros que intentan hacer lo mismo. De cualquier manera, el resultado sería una nueva contribución a StackOverflow: una pregunta que no se responde en ningún otro lado, con, eventualmente, la respuesta correcta de una manera u otra. De hecho, StackOverflow incluso me invitó a responder mi propia pregunta compartiendo mi conocimiento cuando lo publiqué originalmente. Eso fue, de hecho, parte de mi motivación. Incluso los hechos de este asunto no se recogen en ningún lugar que he encontrado.

Asi que:

P. Al usar el AndroidHttpClient para realizar solicitudes REST a través de HTTPS, ¿cómo puedo especificar qué protocolos SSL y sistemas de cifrado usar?

Esto es importante. Se toma muy en cuenta que hay mucho que se puede hacer en el servidor, pero hay límites. El mismo servidor tiene que servir a los navegadores, incluidos los antiguos, así como a otros clientes. Eso significa que el servidor tiene que soportar una amplia gama de protocolos y cifrados. Incluso dentro de Android, si tiene que admitir muchas versiones diferentes, tendrá que admitir varios protocolos y cifrados diferentes.

Más importante aún, de forma predeterminada, OpenSSL respeta la preferencia de cifrado del cliente,no del servidor, durante el protocolo de enlace SSL. Mira estoenviar, por ejemplo, que dice que puede anular ese comportamiento en el cliente configurando SSL_OP_CIPHER_SERVER_PREFERENCE. No está del todo claro si esta opción se puede configurar incluso en un SSLSocket en Java. Incluso si puede, puede configurar la lista de cifrado usted mismo o decirle a su cliente que respete la lista del servidor. De lo contrario, obtendrás el valor predeterminado de Android, sea cual sea la versión en la que estés ejecutando (no la versión con la que te vinculas).

Si toma los valores predeterminados, se puede ver la lista de preferencias enviada por el cliente al servidor por un cliente Jellybean 4.2+aquí, comenzando alrededor de la línea 504. La lista predeterminada de protocolos comienza alrededor de la línea 620. Aunque Jellybean 4.2+ incluye soporte para OpenSSL 1.0.1, particularmente TLSv1.1 y TLSv1.2, estos protocolos no están habilitados de manera predeterminada. Si no haces algo como lo que he hecho, no puedes aprovechar el soporte TLSv1.2, a pesar de que el soporte para TLSv1.2 está anunciado en versiones recientes de Android. Y los detalles varían bastante a medida que regresa a las versiones anteriores de Android. Al menos, es posible que desee echar un vistazo de cerca a los valores predeterminados en todas las versiones compatibles y ver qué está haciendo realmente su cliente. Te sorprenderías.

Hay mucho más que puede decir sobre el soporte para varios protocolos y cifrados. El punto es que a veces puede ser necesario cambiar esta configuración en un cliente.

A. Utilice un SSLSocketFactory personalizado

Esto funcionó bien para mí, y al final, no fue mucho código. Pero fue un poco espinoso por un par de razones:

Hay dos clases diferentes de SSLSocketFactory. El cliente necesita un org.apache.http.conn.ssl.SSLSocketFactory, pero OpenSSL devuelve un javax.net.ssl.SSLSocketFactory. Esto es definitivamente confuso. Utilicé la delegación para hacer una llamada a la otra, sin mucho problema.Cuidado con la diferencia entre OpenSSLContextImpl y SSLContextImpl. Uno simplemente envuelve al otro, pero no son intercambiables. Cuando utilicé el método SSLContextImpl.engineGetSocketFactory: olvido lo que sucedió exactamente, pero algo falló. Asegúrese de usar un OpenSSLContextImpl para obtener su fábrica de socket, no un SSLContextImpl. También puedes usar javax.net.ssl.SSLSocketFactory.getDefault (), pero no estoy seguro.No puedes subclasificar fácilmente AndroidHttpClient, porque su constructor es privado. Esto es desafortunado, ya que proporciona algunos otros buenos beneficios, como asegurarse de que lo cierre correctamente en lugar de perder recursos. El DefaultHttpClient funciona bien. Tomé prestado el método newInstance de AndroidHttpClient (alrededor de la línea 105).

Los puntos clave:

public class SecureSocketFactory extends SSLSocketFactory {
    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        // order should not matter here; the server should select the highest
        // one from this list that it supports
        s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });

        // order matters here; specify in preference order
        s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });

Entonces:

// when creating client
HttpParams params;
SchemeRegistry schemeRegistry = new SchemeRegistry();

// use custom socket factory for https
SSLSocketFactory sf = new SecureSocketFactory();
schemeRegistry.register(new Scheme("https", sf, 443));

// use the default for http
schemeRegistry.register(new Scheme("http",
            PlainSocketFactory.getSocketFactory(), 80));

ClientConnectionManager manager =
            new ThreadSafeClientConnManager(params, schemeRegistry);

HttpClient client = new DefaultHttpClient(manager, params);

Por debajo de Android 3.0 (Honeycomb / SDK 11), las opciones de cifrado admitidas se vuelven más limitadas y hay menos motivación para anular los valores predeterminados. En FROYO / SDK 8, mi SecureSocketFactory explota por alguna razón, y el jurado está a favor de 10. Pero parece que funciona para 11 al alza.

La solución completa está en un github público.repo.

Otra solución podría ser usar una conexión HttpsUrl, que facilita el uso de una fábrica de sockets personalizados, pero imagino que probablemente perderá aún más la comodidad del cliente HTTP de alto nivel. No tengo ninguna experiencia con HttpsUrlConnection.

Respuestas a la pregunta(1)

Su respuesta a la pregunta