1. Introducción
Es común que las apps intercambien datos a través de Internet. Dado que es posible que tu app se comunique con servidores que no sean los de confianza, debes tener cuidado cuando envíes y recibas información que pueda ser sensible y privada.
Qué compilarás
En este codelab, compilarás una app que muestra mensajes. Cada mensaje contendrá el nombre del remitente, el mensaje de texto y una URL que vincule a su "foto de perfil". La app mostrará estos mensajes de la siguiente manera:
|
Qué aprenderás
- Por qué es importante la comunicación segura de la red
- Cómo usar la biblioteca Volley para realizar solicitudes de red
- Cómo usar una configuración de seguridad de red para ayudar a que la comunicación de la red sea más segura
- Cómo modificar algunas opciones avanzadas de la configuración de seguridad de la red, que te ayudarán durante el desarrollo y las pruebas
- Explorarás uno de los problemas de seguridad de red más comunes y descubrirás cómo puedes evitarlo con una configuración de seguridad de la red
Requisitos
- La versión más reciente de Android Studio
- Un dispositivo o emulador de Android que ejecute Android 7.0 (nivel de API 24) o una versión posterior
- Node.js (o acceso a un servidor web configurable)
Si, a medida que avanzas con este codelab, encuentras algún problema (errores de código, errores gramaticales, texto poco claro, etc.), infórmalo mediante el vínculo Informa un error que se encuentra en la esquina inferior izquierda del codelab.
2. Cómo prepararte
Descarga el código
Haz clic en el siguiente vínculo a fin de descargar todo el código de este codelab:
Descomprime el archivo ZIP descargado. Se descomprimirá una carpeta raíz (android-network-secure-config
), que contendrá el proyecto de Android Studio (SecureConfig/
) y algunos archivos de datos que usaremos en una etapa más adelante (server/
).
También puedes ver el código directamente desde GitHub: (comienza con la rama master
).
También preparamos una rama con el código final después de cada paso. Si te bloqueas, revisa las ramas de GitHub o clona el repositorio completo: https://github.com/android/codelab-android-network-security-config/branches/all.
3. Cómo ejecutar la app
Después de hacer clic en el ícono "cargar", esta app accede a un servidor remoto para cargar una lista de mensajes, nombres y URL de fotos de perfil desde un archivo JSON. A continuación, los mensajes se mostrarán en una lista, y la app cargará las imágenes de las URL a las que se hace referencia.
Nota: El propósito de la app que usamos en este codelab es únicamente demostrativo. No se incluye tanto control de errores como se necesitaría en un entorno de producción.
Arquitectura de la app
La app sigue el patrón de MVP, separa el almacenamiento de datos y el acceso a la red (modelo) de la lógica (presentación) y la pantalla (vista).
La clase MainContract
contiene el contrato que describe la interfaz entre la vista y la presentación:
MainContract.java
/*
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.networksecurity;
import com.example.networksecurity.model.Post;
/**
* Contract defining the interface between the View and Presenter.
*/
public interface MainContract {
interface View {
/**
* Sets the presenter for interaction from the View.
*
* @param presenter
*/
void setPresenter(Presenter presenter);
/**
* Displays or hides a loading indicator.
*
* @param isLoading If true, display a loading indicator, hide it otherwise.
*/
void setLoadingPosts(boolean isLoading);
/**
* Displays a list of posts on screen.
*
* @param posts The posts to display. If null or empty, the list should not be shown.
*/
void setPosts(Post[] posts);
/**
* Displays an error message on screen and optionally prints out the error to logcat.
*/
void showError(String title, String error);
/**
* Hides the error message.
*
* @see #showError(String, String)
*/
void hideError();
/**
* Displays an empty message and icon.
*
* @param showMessage If true, the message is show. If false, the message is hidden
*/
void showNoPostsMessage(boolean showMessage);
}
interface Presenter {
/**
* Call to start the application. Sets up initial state.
*/
void start();
/**
* Loads post for display.
*/
void loadPosts();
/**
* An error was encountered during the loading of profile images.
*/
void onLoadPostImageError(String error, Exception e);
}
}
Configuración de la app
A modo de demostración, se inhabilitó el almacenamiento en caché de la red en esta app. Lo ideal sería que, en un entorno de producción, la app utilice una caché local para limitar la cantidad de solicitudes de red remota.
El archivo gradle.properties
contiene la URL desde la que se carga la lista de mensajes:
gradle.properties
postsUrl="http://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"
Cómo compilar y ejecutar la app
- Inicia Android Studio y abre el directorio SecureConfig como proyecto de Android.
- Haz clic en "run" para iniciar la app:
La siguiente captura de pantalla de la app muestra cómo se verá en un dispositivo:
4. Configuración de seguridad de la red básica
En este paso, crearemos una configuración de seguridad de red básica y observaremos un error que se produce cuando se infringe una de las reglas de la configuración.
Descripción general
La configuración de seguridad de la red permite que las apps personalicen su configuración de seguridad de red mediante un archivo de configuración declarativo. Toda la configuración está contenida en este archivo en formato XML, y no necesitas modificar el código.
Permite la configuración de los siguientes elementos:
- Inhabilitación de tráfico de texto simple: Inhabilita el tráfico de texto simple.
- Anclas de confianza personalizadas: Especifican cuáles son las autoridades certificadoras y las fuentes en las que confiará la app.
- Anulaciones exclusivas de depuración: Depuran de forma segura las conexiones protegidas sin afectar las compilaciones de lanzamiento.
- Fijación de certificados: Restringe las conexiones protegidas a certificados específicos.
El archivo se puede organizar por dominios. Esto permite que se aplique la configuración de seguridad de la red a todas las URL o solo a dominios específicos.
La configuración de seguridad de la red está disponible en Android 7.0 (nivel de API 24) y versiones posteriores.
Cómo crear un archivo de configuración de seguridad de red en formato XML
Crea un nuevo archivo de recursos XML con el nombre network_security_config.xml
.
En el Android Project Panel del lado izquierdo, haz clic con el botón derecho en res
y, luego, selecciona New > Android Resource File.
Configura las siguientes opciones y haz clic en OK.
Nombre del archivo |
|
Tipo de recurso |
|
Elemento root |
|
Nombre del directorio |
|
Abre el archivo xml/network_security_config.xml
(si no se abrió automáticamente).
Reemplaza su contenido con el siguiente fragmento:
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" >
</base-config>
</network-security-config>
Esta configuración se aplica a la configuración base o a la configuración de seguridad predeterminada de la app, e inhabilita todo el tráfico de texto simple.
Cómo habilitar la configuración de seguridad de la red
A continuación, agrega una referencia a la configuración de la app en el archivo AndroidManifest.xml
.
Abre el archivo AndroidManifest.xml
y busca el elemento application
.
Primero, quita la línea que configura la propiedad android:usesCleartextTraffic="true"
.
Luego, agrega la propiedad android:networkSecurityConfig
al elemento application
en AndroidManifest, y haz referencia al recurso network_security_config
del archivo en formato XML: @xml/network_security_config
.
Después de quitar y agregar las dos propiedades anteriores, la etiqueta de aplicación de apertura debería verse de la siguiente manera:
AndroidManifest.xml
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:fullBackupContent="false"
tools:ignore="GoogleAppIndexingWarning">
...
Cómo compilar y ejecutar la app
Compila y ejecuta la app.
Verás un error: la app está tratando de cargar datos a través de una conexión de texto simple.
En logcat, notarás este error:
java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted
La app no está cargando datos porque todavía está configurada para cargar la lista de mensajes desde una conexión HTTP sin encriptar. La URL configurada en el archivo gradle.properties
dirige a un servidor HTTP que no usa TLS.
Modifiquemos esta URL para que use otro servidor y cargue los datos mediante una conexión HTTPS segura.
Cambia el archivo gradle.properties
como se muestra a continuación:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"
(Observa el protocolo https en la URL).
Es posible que debas volver a compilar el proyecto para que se detecte este cambio. En el menú, selecciona Build > Rebuild
.
Vuelve a ejecutar la app. Ahora verás la carga de datos porque la solicitud de red usa una conexión HTTPS:
5. Problema común: Actualizaciones del servidor
La configuración de seguridad de red puede protegerte contra vulnerabilidades cuando una app realiza una solicitud a través de una conexión no segura.
Otro problema común del que se encarga la configuración de seguridad de la red son los cambios en el servidor que afectan a las URL cargadas en la app para Android. En el caso de nuestra app, por ejemplo, imagina que el servidor comienza a mostrar URL HTTP no seguras para las imágenes de perfil, en lugar de URL HTTPS seguras. Una configuración de seguridad de red que utiliza conexiones HTTPS generaría una excepción porque este requisito no se cumpliría en el tiempo de ejecución.
Cómo actualizar el backend de la app
Como recordarás, la app primero carga una lista de mensajes, cada uno de los cuales hace referencia a una URL de una foto de perfil.
Imagina que hubo un cambio en los datos que consume la app y hace que solicite URL de imágenes diferentes. Modifiquemos la URL de datos del backend para simular este cambio.
Cambia el archivo gradle.properties
como se muestra a continuación:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v2/posts.json"
(Observa el "v2" en la ruta)
Es posible que debas volver a compilar el proyecto para que se detecte este cambio. En el menú, selecciona Build > Rebuild
.
Puedes acceder al backend "nuevo" desde tu navegador para ver el archivo JSON modificado. Observa que todas las URL a las que se hace referencia usan HTTP y no HTTPS.
Cómo ejecutar la app y examinar el error
Compila y ejecuta la app.
La app carga los mensajes, pero no las imágenes. Examina el mensaje de error en la app y en logcat para ver por qué:
java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted
La app todavía usa HTTPS para acceder al archivo JSON. Sin embargo, los vínculos para crear imágenes de perfil dentro del archivo JSON usan direcciones HTTP, por lo que la app intenta cargar las imágenes en HTTP (no seguro).
Cómo proteger los datos
La configuración de seguridad de la red impidió correctamente una exposición accidental de datos. En lugar de intentar acceder a datos no seguros, la app bloquea el intento de conexión.
Imagina una situación como esta, donde un cambio en el backend no se probó satisfactoriamente antes del lanzamiento. Aplicar una configuración de seguridad de red a tu app para Android puede evitar que ocurran problemas similares, incluso después del lanzamiento de la app.
Cómo cambiar el backend para corregir la app
Cambia las URL del backend a una versión nueva que se "corrigió". En este ejemplo, se simula una corrección mediante la referencia a imágenes de perfil con URL HTTPS correctas.
Cambia la URL de backend en el archivo gradle.properties
y actualiza el proyecto:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v3/posts.json"
(Observa el "v3" en la ruta)
Vuelve a ejecutar la app. Ahora, funciona como estaba previsto:
6. Configuración específica del dominio
Hasta ahora, especificamos la configuración de seguridad de red en base-config
, que aplica la configuración a todas las conexiones que la app intenta realizar.
Puedes anular esta configuración para destinos específicos estableciendo un elemento domain-config
. Una domain-config
declara las opciones de configuración para un conjunto específico de dominios.
Actualicemos la configuración de seguridad de la red en nuestra app a la siguiente:
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
Esta configuración aplica base-config
a todos los dominios, excepto "localhost" y sus subdominios, para los que se aplica una configuración diferente.
Aquí, la configuración básica impide el tráfico de texto simple para todos los dominios. Sin embargo, la configuración del dominio anula la regla y permite que la app acceda a localhost con texto simple.
Cómo realizar pruebas con un servidor HTTP local
Ahora que la app puede acceder a localhost con texto simple, iniciemos un servidor web local y probemos este protocolo de acceso.
Existen varias herramientas que pueden usarse para alojar un servidor web muy básico, como Node.js, Python y PERL. En este codelab usaremos el módulo http-server
de Node.js para entregar los datos de nuestra app.
- Abre una terminal e instala
http-server
:
npm install http-server -g
- Navega al directorio donde revisaste el código y, luego, al directorio
server/
:
cd server/
- Inicia el servidor web y entrega los archivos ubicados en los datos o el directorio de datos:
http-server ./data -p 8080
- Abre un navegador web y ve a http://localhost:8080 para verificar que puedas acceder a los archivos y ver el archivo "
posts.json
":
- A continuación, reenvía el puerto 8080 del dispositivo a la máquina local. En otra ventana de terminal, ejecuta el siguiente comando:
adb reverse tcp:8080 tcp:8080
Ahora tu app puede acceder a "localhost:8080" desde el dispositivo Android.
- Cambia la URL que usaste a fin de cargar datos en la app para que dirija al servidor nuevo en
localhost
. Modifica el archivogradle.properties
de la siguiente manera (recuerda que posiblemente debas sincronizar un proyecto de Gradle después de hacerlo):
gradle.properties
postsUrl="http://localhost:8080/posts.json"
- Ejecuta la app y verifica que los datos se carguen desde la máquina local. Puedes intentar modificar el archivo
data/posts.json
y actualizar la app para confirmar que la configuración nueva funcione según lo previsto.
Apartado: Configuración del dominio
Las opciones de configuración que se aplican a dominios específicos se definen en un elemento domain-config
que puede contener varias entradas domain
que especifican dónde se deberían aplicar las reglas domain-config
. Si varios elementos domain-config
contienen entradas domain
similares, la configuración de seguridad de la red eligirá una configuración que se aplicará a una URL determinada en función de la cantidad de caracteres coincidentes. Se usará la configuración que contenga la entrada domain
que coincida con la mayor cantidad de caracteres (consecutivos) de la URL.
Una configuración de dominio puede aplicarse a varios dominios y también puede incluir subdominios.
En el siguiente ejemplo, se muestra una configuración de seguridad de red que contiene varios dominios. (No cambiará nuestra app, solo es un ejemplo).
<network-security-config>
<domain-config>
<domain includeSubdomains="true">secure.example.com</domain>
<domain includeSubdomains="true">cdn.example.com</domain>
<trust-anchors>
<certificates src="@raw/trusted_roots"/>
</trust-anchors>
</domain-config>
</network-security-config>
Para conocer más detalles, consulta la definición del formato de archivo de configuración.
7. Anulaciones de depuración
Cuando desarrolles y pruebes una app diseñada para realizar solicitudes a través de HTTPS, es posible que debas conectarla a un servidor web local o a un entorno de prueba, tal como lo hicimos en el paso anterior.
En lugar de agregar un uso general para permitir el tráfico de texto simple en este caso de uso o modificar el código, la opción debug-override
de la configuración de seguridad de la red te permitirá establecer opciones de seguridad que se implementen solo cuando la aplicación se ejecute en modo de depuración; es decir, cuando android:debuggable
sea verdadero. Esto es mucho más seguro que usar un código condicional debido a su definición explícita de solo depuración. Play Store también evita que se suban apps depurables, lo que hace que esta opción sea aún más segura.
Cómo habilitar SSL en el servidor web local
Anteriormente, iniciamos un servidor web local que entregaba datos a través de HTTP en el puerto 8080. Ahora, generaremos un certificado SSL autofirmado y lo usaremos para entregar datos mediante HTTPS:
- Cambia al directorio
server/
en una ventana de terminal y, luego, ejecuta los siguientes comandos para generar un certificado (si aún estás ejecutando el servidor HTTP, puedes presionar[CTRL] + [C]
y detenerlo ahora).
# Run these commands from inside the server/ directory! # Create a certificate authority openssl genrsa -out root-ca.privkey.pem 2048 # Sign the certificate authority openssl req -x509 -new -nodes -days 100 -key root-ca.privkey.pem -out root-ca.cert.pem -subj "/C=US/O=Debug certificate/CN=localhost" -extensions v3_ca -config openssl_config.txt # create DER format crt for Android openssl x509 -outform der -in root-ca.cert.pem -out debug_certificate.crt
Esto genera una autoridad certificadora, la firma y produce un certificado en el formato DER requerido para Android.
- Inicia el servidor web con HTTPS con los certificados que acabas de generar:
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem
Cómo actualizar la URL del backend
Modifica la app para que acceda al servidor localhost mediante HTTPS.
Cambia el archivo gradle.properties
:
gradle.properties
postsUrl="https://localhost:8080/posts.json"
Compila y ejecuta la app.
La app fallará y mostrará un error porque el certificado del servidor no es válido:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
La app no puede acceder al servidor web porque este utiliza un certificado autofirmado que no es de confianza como parte del sistema. En lugar de inhabilitar HTTPS, en el próximo paso, agregaremos este certificado autofirmado para el dominio localhost.
Cómo hacer referencia a una autoridad certificadora personalizada
El servidor web ahora entrega los datos usando una autoridad certificadora (CA) autofirmada que ningún dispositivo acepta de forma predeterminada. Si accedes al servidor desde tu navegador, verás una advertencia de seguridad: https://localhost:8080.
A continuación, usaremos la opción debug-overrides
en la configuración de seguridad de la red a fin de permitir esta autoridad certificadora personalizada solo en el dominio localhost
:
- Modifica el archivo
xml/network_security_config.xml
para que contenga lo siguiente:
res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<debug-overrides>
<trust-anchors>
<!-- Trust a debug certificate in addition to the system certificates -->
<certificates src="system" />
<certificates src="@raw/debug_certificate" />
</trust-anchors>
</debug-overrides>
</network-security-config>
Esta configuración inhabilita el tráfico de red de texto simple y, para compilaciones de depuración*,* habilita a la autoridad certificadora proporcionada por el sistema, así como un archivo de certificado almacenado en el directorio res/raw
.
Nota: La configuración de depuración agrega <certificates src="system" />
de forma implícita, por lo que la app funcionará incluso sin eso. Lo incluimos para mostrar cómo la agregarías en una configuración más avanzada.
- A continuación, copia el archivo "
debug_certificate.crt
" del directorioserver/
al directorio de recursosres/raw
de la app en Android Studio. También puedes arrastrar y soltar el archivo en la ubicación correcta dentro de Android Studio.
Es posible que primero debas crear este directorio si no existe.
Para hacerlo, puedes ejecutar los siguientes comandos desde el servidor o directorio; o bien, puedes usar un administrador de archivos o Android Studio para crear la carpeta y copiar el archivo en la ubicación correcta:
mkdir ../SecureConfig/app/src/main/res/raw/ cp debug_certificate.crt ../SecureConfig/app/src/main/res/raw/
Android Studio agregará el archivo debug_certificate.crt
a la lista de archivos debajo de app/res/raw
:
Cómo ejecutar la app
Compila y ejecuta la app, que ahora accede a nuestro servidor web local mediante HTTPS con un certificado de depuración autofirmado.
Si encuentras un error, revisa con atención los resultados del logcat y asegúrate de haber reiniciado el http-server
con las nuevas opciones de línea de comandos. También verifica que el archivo debug_certificate.crt
esté en la ubicación correcta (res/raw/debug_certificate.crt
).
8. Más información
La configuración de seguridad de la red es compatible con muchas funciones más avanzadas, incluidas las siguientes:
- Fijación de certificados
- Autoridades certificadoras de confianza instaladas por el usuario
- Limitación del conjunto de CA de confianza
Cuando uses estas funciones, revisa la documentación para obtener detalles sobre las prácticas recomendadas y las limitaciones.
Cómo hacer que tu app sea más segura
Como parte de este codelab, aprendiste a usar la configuración de seguridad de red con el propósito de que una app para Android sea más segura. Piensa en cómo tu propia app puede usar estas funciones y cómo podrías beneficiarte de una configuración de depuración más sólida para pruebas y desarrollo.