Codelab Configuração de segurança de rede do Android

Apps costumam trocar dados pela Internet. Como seu app pode se comunicar com mais servidores além dos confiáveis, você precisa ter cuidado ao enviar e receber informações confidenciais e privadas.

O que você criará

Neste codelab, você criará um app que exibe mensagens. Cada mensagem conterá o nome do remetente, a mensagem de texto e um URL para a "foto do perfil". O app exibirá essas mensagens da seguinte maneira:

  • Carregue um arquivo JSON que contenha uma lista de mensagens de texto da rede.
  • Carregue cada foto de perfil e programe-as para serem exibidas ao lado da mensagem adequada.

O que você aprenderá

  • Por que a comunicação de rede segura é importante.
  • Como usar a biblioteca Volley para fazer solicitações de rede.
  • Como usar uma Configuração de segurança de rede para tornar a comunicação de rede mais segura.
  • Como modificar algumas opções avançadas da Configuração de segurança de rede que ajudarão durante o desenvolvimento e os testes.
  • Explorar um dos problemas mais comuns de segurança de rede e veja como uma Configuração de segurança de rede pode ajudar a evitá-lo.

Pré-requisitos

  • A versão mais recente do Android Studio.
  • Um dispositivo ou emulador Android com o Android 7.0 (API de nível 24) ou uma versão mais recente.
  • Node.js (ou acesso a um servidor da Web configurável).

Se você encontrar algum problema (bugs no código, erros gramaticais, instruções pouco claras etc.) neste codelab, informe o problema no link "Informar um erro", no canto inferior esquerdo.

Fazer o download do código

Clique no link abaixo para fazer o download de todo o código para este codelab:

Download do código-fonte

Descompacte o arquivo ZIP transferido por download. Essa ação descompactará uma pasta raiz (android-network-secure-config), que contém o projeto do Android Studio (SecureConfig/) e alguns arquivos de dados que serão usados em uma fase posterior (server/).

Também é possível conferir o código diretamente no GitHub: (comece com a ramificação master):

Repositório do GitHub

Além disso, preparamos uma ramificação com o código final após cada etapa. Se você não souber o que fazer, confira as ramificações do GitHub ou clone todo o repositório: https://github.com/googlecodelabs/android-network-security-config/branches/all

Após clicar no ícone "Carregar", este app acessa um servidor remoto para carregar uma lista de mensagens, nomes e URLs das fotos de perfil em um arquivo JSON. Em seguida, as mensagens são exibidas em uma lista, e o app carrega as imagens dos URLs referenciados.

Observação: o app que estamos usando neste codelab é exclusivo para fins de demonstração. Ele não inclui o gerenciamento de erros que você precisaria em um ambiente de produção.

d9e465c94b420ea1.png

Arquitetura de apps

O app segue o padrão MVP, separando o armazenamento de dados e o acesso à rede (modelo) da lógica (apresentador) e da exibição (visualização).

A classe MainContract contém o contrato que descreve a interface entre a Visualização e o Apresentador:

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);
    }

}

Configuração do app

Para fins de demonstração, todo o armazenamento em cache da rede foi desativado neste app. Idealmente, em um ambiente de produção, o app utilizaria um cache local para limitar o número de solicitações remotas de rede.

O arquivo gradle.properties contém o URL que é usado para carregar a lista de mensagens:

gradle.properties

postsUrl="http://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"

Como criar e executar o app

  1. Inicie o Android Studio e abra o diretório SecureConfig como um projeto Android.
  2. Clique em "Run" para iniciar o app: e15973f44eed7cc2.png

A captura de tela do app a seguir mostra a aparência dele em um dispositivo:

63300e7e262bd161.png

Nesta etapa, definiremos uma configuração de segurança de rede básica e observaremos um erro que ocorre quando uma das regras da configuração é violada.

Visão geral

As Configurações de segurança de rede permitem que os apps personalizem as configurações de segurança de rede usando um arquivo de configuração declarativo. Toda a configuração está contida nesse arquivo XML, e nenhuma mudança no código é necessária.

Ele permite configurar o seguinte:

  • Cancelamento do uso de tráfego de texto não criptografado: desativar o tráfego do texto não criptografado.
  • Âncoras de confiança personalizadas: especificar as autoridades de certificação e as fontes em que o app confia.
  • Substituições apenas em depuração: depurar conexões seguras sem afetar os builds de lançamento.
  • Fixação de certificados: restringir conexões seguras a certificados específicos.

O arquivo pode ser organizado por domínios, permitindo que as configurações de segurança de rede sejam aplicadas a todos os URLs ou somente a domínios específicos.

A Configuração de segurança de rede está disponível no Android 7.0 (API de nível 24) e em versões mais recentes.

Criar um arquivo XML da Configuração de segurança de rede

Crie um novo arquivo de recursos XML com o nome network_security_config.xml.

Em Android Project Panel à esquerda, clique com o botão direito do mouse em res e selecione New > Android resource file.

35db6786b96a6980.png

Defina as opções a seguir e clique em OK.

Nome do arquivo

network_security_config.xml

Tipo de recurso

XML

Elemento raiz

network-security-config

Nome do diretório

xml

36ae9e950fe66f1c.png

Abra o arquivo xml/network_security_config.xml, se ele não tiver sido aberto automaticamente.

Substitua o conteúdo pelo seguinte snippet:

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>

Essa configuração se aplica à configuração básica ou à configuração de segurança padrão do app e desativa todo o tráfego de texto não criptografado.

Ativar a Configuração de segurança de rede

Em seguida, adicione uma referência à configuração do app no arquivo AndroidManifest.xml.

Abra o arquivo AndroidManifest.xml e localize o elemento application.

Primeiro, remova a linha que define a propriedade android:usesCleartextTraffic="true".

Em seguida, adicione a propriedade android:networkSecurityConfig ao elemento application no AndroidManifest, referenciando o recurso do arquivo XML network_security_config: @xml/network_security_config

Depois de remover e adicionar as duas propriedades acima, a tag de abertura do aplicativo terá a seguinte aparência:

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">
    ...

Compilar e executar o app

Compile e execute o app.

Você verá um erro: o app está tentando carregar dados em uma conexão de texto não criptografado.

98d8a173d5293742.png

No logcat, você verá este erro:

java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted

O app não está carregando dados porque ainda está configurado para carregar a lista de mensagens de uma conexão HTTP não criptografada. O URL configurado no arquivo gradle.properties aponta para um servidor HTTP que não usa TLS.

Mude esse URL para usar um servidor diferente e carregar os dados em uma conexão HTTPS segura.

Mude o arquivo gradle.properties da seguinte maneira:

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"

Veja o protocolo https no URL.

Pode ser necessário recriar o projeto para que essa mudança seja capturada. No menu, selecione Build > Rebuild.

Execute o app novamente. Agora você verá o carregamento de dados porque a solicitação de rede está usando uma conexão HTTPS:

63300e7e262bd161.png

Uma Configuração de segurança de rede pode proteger contra vulnerabilidades quando um app faz uma solicitação em uma conexão não segura.

Outro problema comum que é abordado por uma Configuração de segurança de rede são as mudanças do servidor que afetam os URLs carregados em um app Android. Por exemplo, no nosso app, imagine que o servidor começou a retornar URLs HTTP não seguros para imagens de perfil em vez de URLs HTTPS seguros. Uma Configuração de segurança de rede que impõe conexões HTTPS geraria uma exceção, já que esse requisito não seria atendido no momento da execução.

Atualizar o back-end do app

Em primeiro lugar, o app carrega uma lista de mensagens referenciando URLs para uma foto de perfil.

Imagine que houve uma mudança nos dados consumidos pelo app, fazendo com que ele solicite URLs de imagem diferentes. Vamos simular essa mudança modificando o URL dos dados de back-end.

Mude o arquivo gradle.properties da seguinte maneira:

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v2/posts.json"

Observe o "v2" no caminho.

Pode ser necessário recriar o projeto para que essa mudança seja capturada. No menu, selecione Build > Rebuild.

Você pode acessar o back-end "novo" no navegador para ver o arquivo JSON modificado. Todos os URLs referenciados usam HTTP em vez de HTTPS.

Executar o app e examinar o erro

Compile e execute o app.

O app carrega mensagens, mas as imagens não estão sendo carregadas. Analise a mensagem de erro no app e no logcat para saber o motivo:

a2a98a842e99168d.png

java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted

O app ainda usa HTTPS para acessar o arquivo JSON. No entanto, os links para imagens de perfil dentro do arquivo JSON usam endereços HTTP, fazendo com que o app tente carregar as imagens por HTTP (não seguro).

Como proteger os dados

A Configuração de segurança de rede impediu uma exposição acidental de dados. Em vez de tentar acessar dados não seguros, o app bloqueia a tentativa de conexão.

Imagine uma situação assim, em que uma mudança no back-end não foi testada o suficiente antes do lançamento. A aplicação de uma Configuração de segurança de rede ao seu app Android pode detectar problemas semelhantes, mesmo depois do lançamento.

Mudar o back-end para corrigir o app

Mude os URLs de back-end para uma nova versão que foi "corrigida". Este exemplo simula uma correção referenciando imagens de perfil com URLs HTTPS corretos.

Mude o URL de back-end no arquivo gradle.properties e atualize o projeto:

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v3/posts.json"

Observe o "v3" no caminho.

Execute o app novamente. Agora ele funciona conforme o esperado:

63300e7e262bd161.png

Até agora, especificamos a configuração de segurança de rede no base-config, que aplica a configuração a todas as conexões que o app tenta fazer.

Você pode substituir essa configuração para destinos específicos especificando um elemento domain-config. Um domain-config declara as opções de configuração para um conjunto específico de domínios.

Atualize a configuração de segurança de rede do nosso app para o seguinte:

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 configuração aplica o base-config a todos os domínios, exceto o "localhost" e os subdomínios dele, a que uma configuração diferente é aplicada.

Aqui, a configuração base impede o tráfego de texto não criptografado para todos os domínios. Ainda assim, a configuração do domínio modifica essa regra, permitindo que o app acesse localhost usando texto não criptografado.

Testar usando um servidor HTTP local

Agora que o app pode acessar o localhost usando texto não criptografado, inicie um servidor da Web local e teste esse protocolo de acesso.

Várias ferramentas podem ser usadas para hospedar um servidor da Web bem básico, incluindo Node.JS, Python e PERL. Neste codelab, usaremos o módulo Node.JS http-server para exibir os dados do nosso app.

  1. Abra um terminal e instale o http-server:
npm install http-server -g
  1. Navegue até o diretório em que você baixou o código e acesse o diretório server/:
cd server/
  1. Inicie o servidor da Web e exiba os arquivos localizados no diretório data/:
http-server ./data -p 8080
  1. Abra um navegador da Web e acesse http://localhost:8080 para verificar se você pode acessar os arquivos e ver o arquivo "posts.json":

934e48553bcc48e7.png

  1. Em seguida, encaminhe a porta 8080 do dispositivo à máquina local. Execute o seguinte comando em outra janela de terminal:
adb reverse tcp:8080 tcp:8080

Agora o app pode acessar "localhost:8080" no dispositivo Android.

  1. Mude o URL usado para carregar dados no app para que ele aponte para o novo servidor no localhost. Mude o arquivo gradle.properties da seguinte maneira (lembre-se de que talvez seja necessário fazer uma sincronização de projeto do Gradle depois de mudar esse arquivo):

gradle.properties

postsUrl="http://localhost:8080/posts.json"
  1. Execute o app e verifique se os dados estão carregados na máquina local. Você pode tentar modificar o arquivo data/posts.json e atualizar o app para confirmar que a nova configuração está funcionando conforme esperado.

63300e7e262bd161.png

Configuração de domínios

As opções de configuração que se aplicam a domínios específicos são definidas em um elemento domain-config. Esse elemento pode conter várias entradas domain que especificam onde as regras domain-config precisam ser aplicadas. Se vários elementos domain-config tiverem entradas domain semelhantes, a Configuração de segurança de rede escolherá uma configuração para aplicar a um determinado URL com base no número de caracteres correspondentes. A configuração com a entrada domain correspondente à maioria dos caracteres (consecutivamente) no URL será usada.

Uma configuração de domínio pode ser aplicada a vários domínios, além de poder incluir subdomínios.

O exemplo a seguir mostra uma Configuração de segurança de rede que contém vários domínios. O app não será mudado. Este é apenas um exemplo.

<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 ver mais detalhes, consulte a definição do formato do arquivo de configuração.

Ao desenvolver e testar um app projetado para fazer solicitações por HTTPS, pode ser necessário conectá-lo a um servidor da Web local ou a um ambiente de teste, como fizemos na etapa anterior.

Em vez de adicionar um uso geral para permitir o tráfego de texto não criptografado nesse caso de uso ou modificar o código, a opção debug-override na Configuração de segurança de rede permite que você defina opções de segurança aplicadas somente quando o app é executado no modo de depuração, ou seja, quando android:debuggable é verdadeiro. Devido à definição explícita para depuração, essa ação é muito mais segura do que o uso de código condicional. A Play Store também impede o upload de apps depuráveis, tornando essa opção ainda mais segura.

Ativar o SSL no servidor da Web local

Anteriormente, iniciamos um servidor da Web local que exibe dados por HTTP na porta 8080. Agora, geraremos um certificado SSL autoassinado e vamos usá-lo para veicular dados por HTTPS:

  1. Mude para o diretório server/ em uma janela de terminal para gerar um certificado. Em seguida, execute os comandos abaixo. Se você ainda estiver executando o servidor http, interrompa-o agora pressionando [CTRL] + [C].
# 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

Essa ação gera uma autoridade de certificação, a assina e gera um certificado no formato DER exigido pelo Android.

  1. Inicie o servidor da Web com HTTPS usando os certificados recém-gerados:
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem

Atualizar o URL de back-end

Mude o app para acessar o servidor localhost via HTTPS.

Mude o arquivo gradle.properties:

gradle.properties

postsUrl="https://localhost:8080/posts.json"

Compile e execute o app.

O app mostrará um erro porque o certificado do servidor é inválido:

3bcce1390e354724.png

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

O app não pode acessar o servidor da Web porque ele usa um certificado autoassinado não confiável como parte o sistema. Em vez de desativar o HTTPS, adicionaremos esse certificado autoassinado ao domínio localhost na próxima etapa.

Referenciar uma autoridade de certificação personalizada

Agora, o servidor da Web está exibindo os dados usando uma autoridade de certificação (CA, na sigla em inglês) autoassinada que não é aceita por padrão por nenhum dispositivo. Se você acessar o servidor pelo navegador, verá um aviso de segurança: https://localhost:8080.

898b69ea4fe9bc21.png

Em seguida, usaremos uma opção debug-overrides na configuração de segurança de rede para permitir essa autoridade de certificação personalizada somente no domínio localhost:

  1. Mude o arquivo xml/network_security_config.xml para que ele contenha o seguinte:

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>

Essa configuração desativa o tráfego de rede de texto não criptografado e, em builds de depuração*,* permite a autoridade de certificação fornecida pelo sistema, bem como um arquivo de certificado armazenado no diretório res/raw.

Observação: a configuração de depuração adiciona <certificates src="system" /> de maneira implícita, de modo que o app funcione mesmo sem isso. Adicionamos esse recurso para mostrar como você o adicionaria em uma configuração mais avançada.

  1. Em seguida, copie o arquivo "debug_certificate.crt" do diretório server/ para o diretório de recursos res/raw do app no Android Studio. Você também pode arrastar e soltar o arquivo no local correto no Android Studio.

Talvez seja necessário criar esse diretório primeiro, se ele não existir.

No diretório server/, execute os seguintes comandos para fazer isso. Você também pode usar um gerenciador de arquivos ou o Android Studio para criar a pasta e copiar o arquivo para o local correto:

mkdir  ../SecureConfig/app/src/main/res/raw/
cp debug_certificate.crt ../SecureConfig/app/src/main/res/raw/

O Android Studio listará o arquivo debug_certificate.crt em app/res/raw:

c3111ae17558e167.png

Executar o app

Compile e execute o app. Agora, ele está acessando nosso servidor da Web local por HTTPS usando um certificado de depuração autoassinado.

Se você encontrar um erro, verifique cuidadosamente a saída do logcat e confira se reiniciou o http-server com as novas opções da linha de comando. Confira também se o arquivo debug_certificate.crt está no local correto (res/raw/debug_certificate.crt).

63300e7e262bd161.png

A Configuração de segurança de rede é compatível com muitos outros recursos avançados, incluindo os seguintes:

Ao usar esses recursos, consulte a documentação para ver detalhes sobre as práticas recomendadas e limitações.

Deixe seu app mais seguro.

Como parte deste codelab, você aprendeu a usar a Configuração de segurança de rede para tornar um app Android mais seguro. Pense em como seu app pode usar esses recursos e como você pode se beneficiar de uma configuração de depuração mais robusta para testes e desenvolvimento.

Saiba mais