Las interacciones encriptadas entre cliente y servidor usan la seguridad de la capa de transporte (TLS) para proteger los datos de tu app.
En este artículo, se analizan las prácticas recomendadas relacionadas con el protocolo de red segura y las consideraciones de la infraestructura de clave pública (PKI). Lee los artículos Descripción general de la seguridad de Android y Descripción general de permisos para obtener más información.
Conceptos
Un servidor con un certificado TLS tiene una clave pública y una privada coincidente. El servidor usa criptografía de clave pública para firmar su certificado durante el protocolo de enlace TLS.
Un protocolo de enlace simple solo demuestra que el servidor conoce la clave privada del certificado. Para abordar esta situación, permite que el cliente confíe en varios certificados. Un servidor determinado no es confiable si su certificado no aparece en el conjunto de certificados de confianza del cliente.
Sin embargo, los servidores pueden usar la rotación de claves para cambiar la clave pública de su certificado por una nueva. El cambio en la configuración del servidor requiere que se actualice la app cliente. Si el servidor es un servicio web de terceros, como un navegador web o una app de correo electrónico, será más difícil saber cuándo actualizar la app cliente.
Por lo general, los servidores dependen de certificados de autoridades certificadoras (AC) para emitir certificados, lo que mantiene la configuración del cliente más estable con el tiempo. Una AC firma un certificado de servidor con su clave privada. Luego, el cliente puede verificar que el servidor tenga un certificado de la AC conocido por la plataforma.
Las AC de confianza suelen enumerarse en la plataforma host. Android 8.0 (nivel de API 26) incluye más de 100 AC que se actualizan en cada versión y no cambian entre dispositivos.
Las apps cliente necesitan un mecanismo para verificar el servidor, ya que la AC ofrece certificados para varios servidores. El certificado de la AC identifica el servidor con un nombre específico, como gmail.com, o con un comodín, como *.google.com.
Para ver la información del certificado del servidor de un sitio web, usa el comando s_client
de la herramienta de openssl
y pasa el número de puerto. De forma predeterminada, HTTPS usa el puerto 443.
El comando transmite el resultado de openssl s_client
a openssl x509
, que formatea la información del certificado según el estándar X.509. El comando solicita el tema (nombre del servidor) y la entidad emisora (AC).
openssl s_client -connect WEBSITE-URL:443 | \ openssl x509 -noout -subject -issuer
Un ejemplo de HTTPS
Si suponemos que tienes un servidor web con un certificado emitido por una AC conocida, puedes realizar una solicitud segura, como se muestra en el siguiente código:
Kotlin
val url = URL("https://wikipedia.org") val urlConnection: URLConnection = url.openConnection() val inputStream: InputStream = urlConnection.getInputStream() copyInputStreamToOutputStream(inputStream, System.out)
Java
URL url = new URL("https://wikipedia.org"); URLConnection urlConnection = url.openConnection(); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out);
Para personalizar las solicitudes HTTP, transmite a HttpURLConnection
.
En la documentación de HttpURLConnection
de Android, se incluyen ejemplos para controlar los encabezados de solicitud y respuesta, publicar contenido, administrar cookies, usar proxies, almacenar respuestas en caché y mucho más. El framework de Android verifica los certificados y los nombres de host con estas APIs.
Usa estas APIs siempre que sea posible. En la siguiente sección, se abordan problemas habituales que requieren soluciones diferentes.
Problemas comunes de la verificación de certificados del servidor
Supongamos que, en lugar de mostrar contenido, getInputStream()
, arroja la siguiente excepción:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374) at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209) at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478) at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433) at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290) at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240) at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282) at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177) at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
Puede producirse por diferentes motivos, entre los cuales se incluyen los siguientes:
- La AC que emitió el certificado del servidor no es conocida.
- El certificado del servidor no está firmado por una AC, sino que está autofirmado.
- Falta una AC intermedia en la configuración del servidor.
En las siguientes secciones, te explicamos la manera de abordar estos problemas sin perder la conexión segura con el servidor.
Autoridad de certificación desconocida
SSLHandshakeException
surge porque el sistema no confía en la AC. Esto podría deberse a que tienes un certificado de una AC nueva en la que Android no confía o a que tu app funciona en una versión anterior sin la AC. Dado que es privada, rara vez se conoce a una AC. Con frecuencia, una AC es desconocida porque no es una AC pública, sino una privada que una entidad (como un Gobierno, una empresa o una institución educativa) emite para uso propio.
Para confiar en AC personalizadas sin necesidad de cambiar el código de tu app, cambia la Configuración de seguridad de la red.
Precaución: Muchos sitios web describen una solución alternativa deficiente que consiste en instalar un TrustManager
que no hace nada.
De esta manera, tus usuarios quedan vulnerables a ataques cuando usan un hotspot de Wi-Fi público, ya que un atacante puede usar trucos de DNS para enviar el tráfico de tus usuarios a través de un proxy que se haga pasar por tu servidor. Luego, el atacante puede registrar contraseñas y otros datos personales. Esto funciona porque el atacante puede generar un certificado y, sin un TrustManager
que valide que el certificado proviene de una fuente confiable, no puedes bloquear este tipo de ataque. Por lo tanto, ni siquiera debes hacer esto a modo de solución temporal. En cambio, haz que tu app confíe en el emisor del certificado del servidor.
Certificado del servidor autofirmado
En segundo lugar, SSLHandshakeException
puede ocurrir debido a un certificado autofirmado, lo que convierte al servidor en su propia AC. Esto es similar a una autoridad certificadora desconocida, por lo que debes modificar la configuración de seguridad de red de tu aplicación para que confíe en tus certificados autofirmados.
Falta de autoridad de certificación intermedia
Tercero, SSLHandshakeException
se produce debido a la falta de una AC intermedia. Las AC públicas rara vez firman certificados del servidor. En su lugar, la AC raíz firma AC intermedias.
Para reducir el riesgo de compromiso, las AC mantienen la AC raíz sin conexión. Sin embargo, los sistemas operativos como Android suelen confiar solo en AC raíz de forma directa, lo que deja una breve brecha de confianza entre el certificado del servidor (firmado por la AC intermedia) y el verificador de certificados, que reconoce la AC raíz.
Para quitar este intervalo de confianza, el servidor envía una cadena de certificados de la AC del servidor a través de cualquier intermediario a una AC raíz de confianza durante el protocolo de enlace TLS.
Por ejemplo, esta es la cadena de certificados mail.google.com, tal como la ve el comando openssl
s_client
:
$ openssl s_client -connect mail.google.com:443 --- Certificate chain 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority ---
Esto muestra que el servidor envía un certificado para mail.google.com emitido por la AC Thawte SGC, que es una AC intermedia, y un segundo certificado para la AC Thawte SGC emitido por una AC Verisign, que es la AC principal en la que Android confía.
Sin embargo, es posible que un servidor no esté configurado para incluir la AC intermedia necesaria. A continuación, te mostramos un ejemplo de un servidor que puede provocar un error en los navegadores Android y excepciones en las apps para Android:
$ openssl s_client -connect egov.uscis.gov:443 --- Certificate chain 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3 ---
A diferencia de una AC desconocida o un certificado del servidor autofirmado, la mayoría de los navegadores de escritorio no producen un error durante la comunicación con este servidor. La caché de los navegadores para computadoras confía en las AC intermedias. Después de aprender sobre una AC intermedia en un sitio, el navegador no la necesitará nuevamente en la cadena de certificados.
Algunos sitios hacen esto intencionalmente para servidores web secundarios que entregan recursos. Para ahorrar ancho de banda, pueden entregar su página HTML principal desde un servidor con una cadena de certificados completa, excepto sus imágenes, CSS y JavaScript sin la AC. Lamentablemente, a veces estos servidores pueden proporcionar un servicio web al que intentas acceder desde tu app para Android, que no es tan tolerante.
Para solucionar este problema, configura el servidor para que incluya la AC intermedia en la cadena de servidores. La mayoría de las AC proporcionan instrucciones sobre cómo hacer esto en servidores web comunes.
Advertencias sobre el uso directo de SSLSocket
Hasta ahora, los ejemplos se centraron en el uso de HttpsURLConnection
por parte de HTTPS.
A veces, las apps deben usar TLS de manera independiente de HTTPS. Por ejemplo, una app de correo electrónico puede usar variantes SSL de SMTP, POP3 o IMAP. En esos casos, la app puede usar SSLSocket
directamente, de la misma manera que HttpsURLConnection
lo hace internamente.
Las técnicas que describimos hasta ahora para abordar los problemas de verificación de certificados también se aplican a SSLSocket
.
De hecho, al usar un TrustManager
personalizado, lo que se pasa a HttpsURLConnection
es un SSLSocketFactory
.
Por lo tanto, si necesitas usar un TrustManager
personalizado con un SSLSocket
, sigue los mismos pasos y usa ese SSLSocketFactory
para crear tu SSLSocket
.
Precaución: SSLSocket
no realiza la verificación del nombre de host. Tu app realiza su propia verificación de nombre de host, preferiblemente llamando a getDefaultHostnameVerifier()
con el nombre de host esperado.
Además, ten en cuenta que HostnameVerifier.verify()
no arroja una excepción para el error. En su lugar, muestra un resultado booleano que debes verificar de forma explícita.
AC bloqueadas
TLS depende de las AC para emitir certificados solo a los propietarios verificados de los servidores y los dominios. En situaciones poco frecuentes, las AC se someten a engaños o, en el caso de Comodo o DigiNotar, a infracciones, y se generan certificados de emisión de un nombre de host para alguien que no es el propietario del servidor o el dominio.
Para mitigar este riesgo, Android puede agregar ciertos certificados o incluso AC completas a una lista de bloqueo. Si bien esta lista históricamente se incorporaba en el sistema operativo, a partir de Android 4.2 se puede actualizar de forma remota para abordar compromisos futuros.
Cómo restringir tu app a certificados específicos
Precaución: En el caso de las apps para Android, no se recomienda la fijación de certificados, la práctica de restringir los certificados que se consideran válidos para tu app a aquellos que ya autorizaste con anterioridad. Los cambios futuros en la configuración del servidor, como el cambio a otra AC, procesarán las apps con certificados fijados que no podrán conectarse al servidor sin recibir una actualización del software del cliente.
Si deseas restringir tu app para que solo acepte certificados que especifiques, es fundamental incluir varias fijaciones de respaldo, incluida al menos una clave que esté completamente bajo tu control y un período de vencimiento lo suficientemente corto para evitar problemas de compatibilidad. La Configuración de seguridad de la red proporciona fijación con estas capacidades.
Certificados de cliente
Este artículo se centra en el uso de TLS para proteger las comunicaciones con los servidores. TLS también admite la noción de certificados de cliente que permiten al servidor validar la identidad de un cliente. Si bien están fuera del alcance de este artículo, las técnicas empleadas son similares a la especificación de un TrustManager
personalizado.
Nogotofail: una herramienta para probar la seguridad del tráfico de red
Nogotofail es una herramienta que te permite confirmar fácilmente que tus apps estén protegidas frente a vulnerabilidades y configuraciones incorrectas conocidas de TLS y SSL. Se trata de una herramienta automatizada, poderosa y escalable para comprobar la presencia de problemas de seguridad de la red en cualquier dispositivo cuyo tráfico de red pueda verse afectado.
Nogotofail se aplica a los siguientes tres casos prácticos principales:
- Detección de errores y vulnerabilidades
- Verificación de soluciones y búsqueda de regresiones
- Comprensión de las aplicaciones y los dispositivos que generan tráfico
Nogotofail es compatible con Android, iOS, Linux, Windows, ChromeOS, macOS y, de hecho, con cualquier dispositivo que uses para conectarte a Internet. Hay un cliente disponible para definir la configuración y recibir notificaciones en Android y Linux. Además, el motor de ataque mismo se puede implementar como router, servidor de VPN o proxy.
Puedes acceder a la herramienta en el Proyecto de código abierto de Nogotofail.
Actualizaciones de SSL y TLS
Android 10
Algunos navegadores, como Google Chrome, les permiten a los usuarios elegir un certificado cuando un servidor TLS envía un mensaje de solicitud de certificado como parte de un protocolo de enlace TLS. A partir de Android 10, los objetos de KeyChain respetan los emisores y los parámetros de especificación de claves cuando llaman a KeyChain.choosePrivateKeyAlias()
para mostrarles a los usuarios un mensaje de selección de certificado. En particular, esta solicitud no contiene opciones que no cumplen con las especificaciones del servidor.
Si no hay ningún certificado disponible para que el usuario seleccione, como sucede cuando no hay ninguno que cumpla con las especificaciones del servidor o un dispositivo no tiene certificados instalados, no se mostrará la solicitud de selección de certificado.
Además, en Android 10 o versiones posteriores, no es necesario tener un bloqueo de pantalla del dispositivo para importar claves o certificados de la AC al objeto KeyChain.
TLS 1.3 habilitado de forma predeterminada
En Android 10 y versiones posteriores, TLS 1.3 está habilitado de forma predeterminada para todas las conexiones de TLS. A continuación, se muestran algunos detalles importantes sobre nuestra implementación de TLS 1.3:
- Los conjuntos de algoritmos de cifrado de TLS 1.3 no se pueden personalizar. Aquellos que son compatibles siempre están habilitados cuando TLS 1.3 está habilitado. Se ignorará cualquier intento de inhabilitarlos con una llamada a
setEnabledCipherSuites()
. - Cuando se negocia TLS 1.3, se llama a los objetos
HandshakeCompletedListener
antes de que se agreguen sesiones a la caché de sesiones. (En TLS 1.2 y otras versiones anteriores, se llama a estos objetos luego de que se agregan las sesiones a la caché de sesiones). - En algunas situaciones en las que las instancias de SSLEngine arrojan
SSLHandshakeException
en versiones anteriores de Android, estas instancias, en cambio, arrojanSSLProtocolException
Android 10 y versiones posteriores. - No se admite el modo 0-RTT.
Si lo deseas, puedes obtener un SSLContext que tenga TLS 1.3 inhabilitado con una llamada a SSLContext.getInstance("TLSv1.2")
. También puedes habilitar o inhabilitar versiones de protocolo por conexión si llamas a setEnabledProtocols()
en un objeto correspondiente.
Los certificados firmados con SHA-1 no son confiables en TLS
En Android 10, los certificados que usan el algoritmo de hash SHA-1 no se consideran confiables en las conexiones de TLS. La AC raíz no emite esos certificados desde 2016, y ya no se consideran confiables en Chrome ni en otros navegadores importantes.
Fallará cualquier intento de establecer conexión con un sitio que presente un certificado que usa SHA-1.
Mejoras y cambios en el comportamiento de KeyChain
Algunos navegadores, como Google Chrome, les permiten a los usuarios elegir un certificado cuando un servidor TLS envía un mensaje de solicitud de certificado como parte de un protocolo de enlace TLS. A partir de Android 10, los objetos KeyChain
respetan los emisores y los parámetros de especificación de claves cuando llaman a KeyChain.choosePrivateKeyAlias()
para mostrarles a los usuarios un mensaje de selección de certificado. En particular, esta solicitud no contiene opciones que no cumplen con las especificaciones del servidor.
Si no hay ningún certificado disponible para que el usuario seleccione, como sucede cuando no hay ninguno que cumpla con las especificaciones del servidor o un dispositivo no tiene certificados instalados, no se mostrará la solicitud de selección de certificado.
Además, en Android 10 o versiones posteriores, no es necesario tener un bloqueo de pantalla del dispositivo para importar claves o certificados de la AC al objeto KeyChain.
Otros cambios en la criptografía y en TLS
Se realizaron pequeños cambios en las bibliotecas de criptografía y TLS que se aplicarán en Android 10:
- Los cifrados "AES/GCM/NoPadding" y "ChaCha20/Poly1305/NoPadding" devuelven tamaños de búfer más precisos desde
getOutputSize()
. - El conjunto de algoritmos de cifrado TLS_FALLBACK_SCSV se omite de los intentos de conexión con un protocolo máximo de TLS 1.2 o superior. Como se incorporaron mejoras en las implementaciones del servidor TLS, no recomendamos revertir a la configuración de TLS externo. En su lugar, recomendamos basarse en la negociación de versiones de TLS.
- ChaCha20-Poly1305 es un alias para ChaCha20/Poly1305/NoPadding.
- Los nombres de hosts con puntos finales no se consideran nombres de hosts de SNI válidos.
- Se respeta la extensión supported_signature_algorithms admitida en CertificateRequest cuando se elige una clave de firma para respuestas de certificados.
- Las claves de firma opacas, como las del almacén de claves de Android, se pueden usar con firmas RSA-PSS en TLS.
Cambios en la conexión HTTPS
Si una app que ejecuta Android 10 pasa un valor nulo a setSSLSocketFactory()
, se produce una IllegalArgumentException
. En las versiones anteriores, pasar null en setSSLSocketFactory()
tenía el mismo efecto que pasar el valor de fábrica predeterminado actual.
Android 11
Los sockets SSL usan el motor SSL de Conscrypt de forma predeterminada
La implementación predeterminada de SSLSocket de Android se basa en Conscrypt
. Desde Android 11, esa implementación se compila internamente sobre el SSLEngine de Conscrypt.