1. Introdução
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:
|
O que você vai 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 ver 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 neste codelab (bugs no código, erros gramaticais, instruções pouco claras etc.), use o link "Informar um erro" no canto inferior esquerdo.
2. Etapas da configuração
Fazer o download do código
Clique no link abaixo para fazer o download de todo o código para este codelab:
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
):
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/android/codelab-android-network-security-config/branches/all (link em inglês).
3. Executar o app
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. Esses dados estão 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 tratamento de erros de que você precisaria em um ambiente de produção.
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
- Inicie o Android Studio e abra o diretório SecureConfig como um projeto Android.
- Clique em "Run" para iniciar o app:
A captura de tela do app a seguir mostra a aparência dele em um dispositivo:
4. Configuração básica de segurança de rede
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 a personalização da segurança dos apps 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.
Defina as opções a seguir e clique em OK.
File name |
|
Resource type |
|
Root element |
|
Directory name |
|
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.
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"
É possível notar 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 os dados são carregados porque a solicitação de rede está usando uma conexão HTTPS:
5. Problema comum: atualizações do servidor
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:
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:
6. Configuração específica do domínio
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.
- Abra um terminal e instale o
http-server
:
npm install http-server -g
- Navegue até o diretório em que você baixou o código e acesse o diretório
server/
:
cd server/
- Inicie o servidor da Web e exiba os arquivos localizados no diretório data/:
http-server ./data -p 8080
- Abra um navegador da Web e acesse http://localhost:8080 para verificar se você pode acessar os arquivos e ver o arquivo "
posts.json
":
- 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.
- Mude o URL usado para carregar dados no app para que ele aponte para o novo servidor no
localhost
. Mude o arquivogradle.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"
- 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.
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.
7. Substituições de depuraçã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:
- 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.
- 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:
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.
Em seguida, vamos usar 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
:
- 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.
- Em seguida, copie o arquivo "
debug_certificate.crt
" do diretórioserver/
para o diretório de recursosres/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
:
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
).
8. Saiba mais
A Configuração de segurança de rede é compatível com muitos outros recursos avançados, incluindo os seguintes:
- Fixação de certificados
- Confiança em autoridades de certificação instaladas pelo usuário
- Limitação do conjunto de CAs confiáveis
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.