Atelier de programmation sur la configuration de la sécurité du réseau Android

1. Introduction

Il est courant que les applications échangent des données via Internet. Étant donné que votre application peut communiquer avec des serveurs autres que ceux qu'elle considère comme fiables, vous devez faire preuve de prudence lorsque vous envoyez et recevez des informations sensibles et privées.

Objectif de cet atelier

Dans cet atelier de programmation, vous créerez une application qui affiche des messages. Chaque message contiendra le nom de l'expéditeur, le message texte et l'URL vers sa "photo de profil". L'application affichera ces messages en procédant comme suit :

  • Chargement d'un fichier JSON contenant une liste de messages texte à partir du réseau
  • Chargement de chaque photo de profil et affichage de ces photos à côté du message approprié

Points abordés

  • Pourquoi une communication réseau sécurisée est-elle importante ?
  • Utiliser la bibliothèque Volley pour effectuer des requêtes réseau
  • Utiliser une configuration de sécurité réseau pour sécuriser davantage la communication réseau
  • Modifier des options avancées de configuration de la sécurité réseau qui seront utiles pour le développement et les tests
  • Explorer l'un des problèmes de sécurité réseau les plus courants et découvrir comment une configuration de sécurité réseau peut l'éviter

Ce dont vous aurez besoin

  • La dernière version d'Android Studio
  • Un appareil ou un émulateur Android fonctionnant sous Android 7.0 (niveau d'API 24) ou version ultérieure
  • Node.js (ou accès à un serveur Web configurable)

Si vous rencontrez des problèmes (bugs de code, erreurs grammaticales, formulation peu claire, etc.) au cours de cet atelier de programmation, veuillez les signaler via le lien "Signaler une erreur" situé dans l'angle inférieur gauche de l'atelier de programmation.

2. Configuration

Télécharger le code

Cliquez sur le lien ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation :

Décompressez le fichier ZIP téléchargé. Cette opération a pour effet de décompresser un dossier racine (android-network-secure-config) contenant le projet Android Studio (SecureConfig/) et certains fichiers de données que nous utiliserons ultérieurement (server/).

Vous pouvez également consulter le code directement sur GitHub (commencez par la branche master) :

Nous avons également préparé une branche avec le code final après chaque étape. Si vous rencontrez des difficultés, consultez les branches sur GitHub ou clonez l'intégralité du dépôt : https://github.com/android/codelab-android-network-security-config/branches/all

3. Exécuter l'application

Après avoir cliqué sur l'icône de chargement, cette application accède à un serveur distant afin de charger une liste de messages, de noms et d'URL vers les photos de profil correspondantes à partir d'un fichier JSON. Ensuite, les messages s'affichent dans une liste, et l'application charge les images à partir des URL référencées.

Remarque : L'application que nous utilisons dans cet atelier de programmation n'est proposée qu'à des fins de démonstration. Elle n'implique pas autant d'erreurs à gérer que dans un environnement de production.

d9e465c94b420ea1.png

Architecture de l'application

L'application suit le modèle MVP (Model-View-Presenter), qui sépare le stockage de données et l'accès réseau (modèle) de la logique (présentateur) et de l'affichage (vue).

La classe MainContract contient le contrat qui décrit l'interface entre la vue et le présentateur :

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

}

Configuration de l'application

À des fins de démonstration, la mise en cache du réseau a été désactivée pour cette application. Idéalement, dans un environnement de production, l'application utiliserait un cache local pour limiter le nombre de requêtes réseau distantes.

Le fichier gradle.properties contient l'URL à partir de laquelle la liste de messages est chargée :

gradle.properties

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

Compiler et exécuter l'application

  1. Lancez Android Studio, puis ouvrez le répertoire SecureConfig en tant que projet Android.
  2. Cliquez sur "Exécuter" pour démarrer l'application : e15973f44eed7cc2.png

La capture d'écran suivante, tirée de l'application, montre comment elle se présente sur un appareil :

63300e7e262bd161.png

4. Configuration de base de la sécurité réseau

Au cours de cette étape, nous allons définir une configuration de base de la sécurité réseau et observer une erreur qui se produit lorsqu'une des règles de configuration n'est pas respectée.

Présentation

La configuration de la sécurité réseau permet aux applications de personnaliser leurs paramètres de sécurité réseau via un fichier de configuration déclaratif. L'intégralité de la configuration se trouve dans ce fichier XML. Aucune modification du code n'est nécessaire.

Il permet de configurer les fonctionnalités suivantes :

  • Désactivation du trafic en texte clair : en effet, vous pouvez désactiver le trafic en texte clair.
  • Ancres de confiance personnalisées : spécifiez les autorités de certification et les sources approuvées par l'application.
  • Forçages réservés au débogage : déboguez les connexions sécurisées sans prendre de risque et sans affecter les builds.
  • Épinglage de certificat : limitez les connexions sécurisées à certains certificats

Le fichier peut être organisé par domaines, ce qui permet d'appliquer les paramètres de sécurité réseau à toutes les URL ou uniquement à des domaines spécifiques.

La configuration de la sécurité réseau est disponible sur Android 7.0 (niveau d'API 24) ou version ultérieure.

Créer un fichier XML de configuration de la sécurité réseau

Créez un fichier de ressources XML nommé network_security_config.xml.

Dans le panneau de projet Android à gauche, effectuez un clic droit sur res, puis sélectionnez Nouveau > Fichier de ressources Android.

35db6786b96a6980.png

Définissez les options suivantes, puis cliquez sur OK.

Nom du fichier

network_security_config.xml

Type de ressource

XML

Élément racine

network-security-config

Nom du répertoire

xml

36ae9e950fe66f1c.png

Ouvrez le fichier xml/network_security_config.xml (s'il ne s'est pas ouvert automatiquement).

Remplacez son contenu par l'extrait suivant :

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>

Cette configuration s'applique à la configuration de base, ou configuration de sécurité par défaut, de l'application et désactive tout le trafic en texte clair.

Activer la configuration de la sécurité réseau

Ensuite, ajoutez une référence à la configuration de l'application dans le fichier AndroidManifest.xml.

Ouvrez le fichier AndroidManifest.xml et localisez-y l'élément application.

Commencez par supprimer la ligne qui définit la propriété android:usesCleartextTraffic="true".

Ensuite, ajoutez la propriété android:networkSecurityConfig à l'élément application du fichier AndroidManifest, en référençant la ressource de fichier XML network_security_config : @xml/network_security_config

Après avoir supprimé la ligne et ajouté les deux propriétés ci-dessus, la balise d'ouverture de l'application devrait se présenter comme suit :

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

Compiler et exécuter l'application

Compilez et exécutez l'application.

Un message d'erreur s'affiche : l'application essaie de charger des données via une connexion en texte clair.

98d8a173d5293742.png

Dans logcat, vous remarquerez l'erreur suivante :

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

L'application ne charge pas de données, car elle est toujours configurée pour charger la liste des messages depuis une connexion HTTP non chiffrée. L'URL configurée dans le fichier gradle.properties pointe vers un serveur HTTP qui n'utilise pas le protocole TLS.

Modifions cette URL pour qu'elle utilise un autre serveur et qu'elle charge les données via une connexion HTTPS sécurisée.

Modifiez le fichier gradle.properties comme suit :

gradle.properties

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

Notez le protocole https dans l'URL.

Vous devrez peut-être recompiler le projet pour que cette modification soit prise en compte. Dans le menu, sélectionnez Build > Rebuild.

Exécutez à nouveau l'application. Les données se chargent maintenant, car la requête réseau utilise une connexion HTTPS :

63300e7e262bd161.png

5. Problème courant : mises à jour côté serveur

Une configuration de sécurité réseau peut vous protéger contre les failles lorsqu'une application envoie une requête via une connexion non sécurisée.

Les modifications côté serveur qui affectent les URL chargées dans une application Android sont un autre problème courant auquel répond la configuration de la sécurité réseau. Par exemple, dans notre application, imaginez que le serveur commence à renvoyer des URL HTTP non sécurisées pour les images de profil au lieu d'URL HTTPS sécurisées. Dans ce cas, une configuration de sécurité réseau qui impose des connexions HTTPS génère une exception, car cette exigence n'est pas respectée lors de l'exécution.

Mettre à jour le backend de l'application

Pour rappel, l'application charge d'abord une liste de messages. Chacun d'eux renvoie à une URL vers une photo de profil.

Imaginez que les données consommées par l'application changent et qu'elle demande d'autres URL d'image. Simulons ce scénario en modifiant l'URL des données du backend.

Modifiez le fichier gradle.properties comme suit :

gradle.properties

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

Notez la version v2 dans le chemin d'accès.

Vous devrez peut-être recompiler le projet pour que cette modification soit prise en compte. Dans le menu, sélectionnez Build > Rebuild.

Vous pouvez accéder au "nouveau" backend depuis votre navigateur pour afficher le fichier JSON modifié. Comme vous pouvez le remarquer, toutes les URL référencées utilisent HTTP au lieu de HTTPS.

Exécuter l'application et examiner l'erreur

Compilez et exécutez l'application.

L'application charge les messages, mais pas les images. Examinez le message d'erreur dans l'application et dans logcat pour en découvrir la raison :

a2a98a842e99168d.png

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

L'application utilise toujours HTTPS pour accéder au fichier JSON. Toutefois, les liens vers les images de profil dans le fichier JSON utilisent des adresses HTTP. Par conséquent, l'application tente de charger les images via HTTP (ce qui n'est pas sécurisé).

Protéger les données

La configuration de la sécurité réseau a permis d'éviter une exposition accidentelle des données. Au lieu d'essayer d'accéder à des données non sécurisées, l'application bloque la tentative de connexion.

Imaginez un scénario dans lequel une modification du backend n'a pas été suffisamment testée avant le déploiement. L'application d'une configuration de sécurité réseau à votre application Android contribue à éviter certains problèmes similaires, même après le lancement de l'application.

Modifier le backend pour corriger l'application

Remplacez les URL du backend par une nouvelle version qui a été "corrigée". Cet exemple simule un correctif qui référence les images de profil à l'aide des URL HTTPS appropriées.

Modifiez l'URL du backend dans le fichier gradle.properties et actualisez le projet :

gradle.properties

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

Notez la version v3 dans le chemin d'accès.

Exécutez à nouveau l'application. Elle fonctionne désormais comme prévu :

63300e7e262bd161.png

6. Configuration spécifique au domaine

Jusqu'à présent, nous avons spécifié la configuration de la sécurité réseau dans base-config, qui applique cette configuration à toutes les connexions que l'application tente d'établir.

Si vous souhaitez ignorer cette configuration pour des destinations spécifiques, définissez un élément domain-config. L'élément domain-config déclare des options de configuration pour un ensemble spécifique de domaines.

Mettons à jour la configuration de sécurité réseau dans notre application comme suit :

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>

Cette configuration applique base-config à tous les domaines, à l'exception du domaine localhost et de ses sous-domaines, auxquels une autre configuration est appliquée.

Ici, la configuration de base empêche le trafic en texte clair pour tous les domaines. Cependant, la configuration du domaine ignore cette règle et permet à l'application d'accéder à localhost en texte clair.

Effectuer des tests à l'aide d'un serveur HTTP local

Maintenant que l'application peut accéder à localhost en texte clair, nous allons démarrer un serveur Web local et tester ce protocole d'accès.

Différents outils peuvent être utilisés pour héberger un serveur Web très basique, y compris Node.JS, Python et Perl. Dans cet atelier de programmation, nous utiliserons le module Node.js http-server pour diffuser les données de notre application.

  1. Ouvrez un terminal et installez http-server :
npm install http-server -g
  1. Accédez au répertoire dans lequel vous avez extrait le code, puis au répertoire server/ :
cd server/
  1. Démarrez le serveur Web et diffusez les fichiers situés dans le répertoire data/ :
http-server ./data -p 8080
  1. Ouvrez un navigateur Web et accédez à http://localhost:8080 pour vérifier que vous pouvez accéder aux fichiers et que le fichier posts.json s'y trouve :

934e48553bcc48e7.png

  1. Transférez ensuite le port 8080 de l'appareil vers la machine locale. Exécutez la commande suivante dans une autre fenêtre de terminal :
adb reverse tcp:8080 tcp:8080

Votre application peut désormais accéder à l'adresse "localhost:8080" depuis l'appareil Android.

  1. Modifiez l'URL utilisée pour charger les données dans l'application afin qu'elle pointe vers le nouveau serveur sur localhost. Modifiez le fichier gradle.properties comme suit (n'oubliez pas que vous devrez peut-être synchroniser le projet Gradle après avoir modifié ce fichier) :

gradle.properties

postsUrl="http://localhost:8080/posts.json"
  1. Exécutez l'application et vérifiez que les données sont chargées à partir de la machine locale. Vous pouvez essayer de modifier le fichier data/posts.json et d'actualiser l'application pour vérifier que la nouvelle configuration fonctionne comme prévu.

63300e7e262bd161.png

Aparté sur la configuration des domaines

Les options de configuration qui s'appliquent à des domaines spécifiques sont définies dans un élément domain-config. Cet élément peut contenir plusieurs entrées domain qui spécifient l'endroit où les règles domain-config doivent s'appliquer. Si plusieurs éléments domain-config contiennent des entrées domain similaires, la configuration de sécurité réseau choisit une configuration à appliquer à une URL donnée en fonction du nombre de caractères correspondants. La configuration contenant l'entrée domain qui correspond le plus aux caractères (consécutifs) de l'URL est utilisée.

Une configuration de domaine peut s'appliquer à plusieurs domaines et peut également inclure des sous-domaines.

L'exemple suivant présente une configuration de sécurité réseau contenant plusieurs domaines. Notez que nous ne modifions pas notre application. Il ne s'agit que d'un exemple.

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

Pour en savoir plus, consultez la définition du format de fichier de configuration.

7. Utiliser l'option "debug-override"

Lorsque vous développez et testez une application conçue pour envoyer des requêtes via HTTPS, vous devrez peut-être la connecter à un serveur Web ou à un environnement de test local, comme nous l'avons fait à l'étape précédente.

Au lieu d'autoriser globalement le trafic en texte clair pour ce cas d'utilisation ou de modifier le code, l'option debug-override dans la configuration de la sécurité réseau vous permet de définir des options de sécurité qui ne s'appliquent que lorsque l'application est exécutée en mode débogage, c'est-à-dire lorsque android:debuggable a la valeur "true". Cette méthode, qui est réservée au débogage, est bien plus sûre que l'utilisation de code conditionnel. Le Play Store empêche également l'importation d'applications débogables, ce qui rend cette option encore plus sûre.

Activer SSL sur le serveur Web local

Précédemment, nous avons démarré un serveur Web local qui diffusait des données via HTTP sur le port 8080. Nous allons maintenant générer un certificat SSL autosigné et l'utiliser pour diffuser des données via HTTPS :

  1. Pour générer un certificat, accédez au répertoire server/ dans une fenêtre de terminal, puis exécutez les commandes suivantes (si vous exécutez toujours le serveur HTTP, vous pouvez l'arrêter maintenant en appuyant sur [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

Cette opération génère une autorité de certification, la signe et génère un certificat au format DER requis par Android.

  1. Démarrez le serveur Web avec HTTPS, à l'aide des certificats que vous venez de générer :
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem

Mettre à jour l'URL du backend

Modifiez l'application pour accéder au serveur localhost via HTTPS.

Modifiez le fichier gradle.properties :

gradle.properties

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

Compilez et exécutez l'application.

L'application échoue et génère une erreur, car le certificat du serveur n'est pas valide :

3bcce1390e354724.png

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

L'application ne parvient pas à accéder au serveur Web, car celui-ci utilise un certificat autosigné qui n'est pas approuvé par le système. Au lieu de désactiver HTTPS, nous ajouterons ce certificat autosigné pour le domaine localhost à l'étape suivante.

Référencer une autorité de certification personnalisée

Le serveur Web diffuse désormais les données à l'aide d'une autorité de certification (CA) autosignée, qui n'est acceptée par aucun appareil par défaut. Si vous accédez au serveur depuis votre navigateur, vous verrez l'avertissement de sécurité : https://localhost:8080

898b69ea4fe9bc21.png

Nous allons maintenant utiliser une option debug-overrides dans la configuration de la sécurité réseau afin de n'autoriser cette autorité de certification personnalisée que pour le domaine localhost :

  1. Modifiez le fichier xml/network_security_config.xml pour qu'il contienne le code suivant :

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>

Cette configuration désactive le trafic réseau en texte clair et, pour les versions de débogage, active l'autorité de certification fournie par le système, ainsi qu'un fichier de certificat stocké dans le répertoire res/raw.

Remarque : La configuration de débogage ajoute implicitement <certificates src="system" />. L'application fonctionnerait donc même sans cela. Nous l'avons ajouté pour vous montrer comment l'inclure dans une configuration plus avancée.

  1. Ensuite, copiez le fichier debug_certificate.crt depuis le répertoire server/ dans le répertoire de ressources res/raw de l'application dans Android Studio. Vous pouvez également glisser-déposer le fichier vers l'emplacement approprié dans Android Studio.

Vous devrez peut-être d'abord créer ce répertoire s'il n'existe pas.

Pour ce faire, vous pouvez exécuter les commandes suivantes à partir du serveur ou du répertoire. Sinon, utilisez un gestionnaire de fichiers ou Android Studio pour créer le dossier et le copier à l'emplacement approprié :

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

Android Studio affiche maintenant le fichier debug_certificate.crt sous app/res/raw :

c3111ae17558e167.png

Exécuter l'application

Compilez et exécutez l'application. Celle-ci accède désormais au serveur Web local via HTTPS à l'aide d'un certificat de débogage autosigné.

Si vous rencontrez une erreur, vérifiez attentivement la sortie logcat et assurez-vous d'avoir redémarré http-server avec les nouvelles options de ligne de commande. Vérifiez également que le fichier debug_certificate.crt est au bon endroit (res/raw/debug_certificate.crt).

63300e7e262bd161.png

8. En savoir plus

La configuration de la sécurité réseau est compatible avec de nombreuses autres fonctionnalités avancées, y compris les suivantes :

Lorsque vous utilisez ces fonctionnalités, consultez la documentation pour en savoir plus sur les bonnes pratiques et les limites en vigueur.

Sécuriser l'application

Dans cet atelier de programmation, vous avez appris à utiliser une configuration de sécurité réseau pour renforcer la sécurité d'une application Android. Réfléchissez à la manière dont votre propre application pourrait exploiter ces fonctionnalités et aux avantages dont vous pourriez bénéficier avec une configuration de débogage plus robuste pour les tests et le développement.

En savoir plus