Lors du derniers Ch’ti JUG présenté par David Gageot sur le thème du Cloud, plusieurs services et frameworks ont été présentés ce soir là. Parmi ceux-ci, il y avait notamment GitHub-Pages, le framework javascript AngularJS et la plateforme d’application Heroku.

Le framework javascript AngularJS entrainant avec lui son lot de questions concernant l’authentification des utilisateurs et de leurs requêtes REST. Nous allons ici vous présenter les différentes techniques que vous pourriez utiliser afin de sécuriser vos services REST. Nous commencerons par les plus simples, et nous les améliorerons au fur et à mesure pour nous approcher des choix d’API robustes tels que celle d’Amazon Web Services.

1- REST : Petits récapitulatifs et cas d’étude

Sans entrer dans les détails (car ce n’est pas l’objectif de cet article), une architecture de type REST (« Representational State Transfer ») permet de rendre toutes informations accessible via une URL. Celles-ci sont alors appelées ressources et sont managées par le biais d’un ensemble de méthodes HTTP.

Les principales méthodes HTTP utilisées sont les suivantes :

  • GET    ? Récupérer une ressource (ne modifie pas la ressource).
  • POST   ? Modifier une ressource.
  • PUT    ? Créer une ressource.
  • DELETE ? Supprimer une ressource.

Prenons comme cas d’étude celui d’une application REST permettant de gérer des utilisateurs et leurs livres (qui seront donc nos ressources). Elles seront accessibles par des requêtes HTTP de ce type :

GET : http://www.rest-books.com/services/users/1/books
? Retourne tous les livres de l’utilisateur 1.

GET : http://www.rest-books.com/services/users/1/books/123
? Retourne le livre 123 appartenant à l’utilisateur 1.

DELETE : http://www.rest-books.com/services/users/1
? Supprime l’utilisateur 1.


: Ça fonctionne.
: L’utilisateur 1 peut accéder aux livres de l’utilisateur 2 en modifiant simplement l’URL d’appel de la ressource par celle-ci : http://www.rest-books.com/services/users/2/books.

2- Authentification : Login / Password

Pour palier à ce problème, une solution serait d’ajouter le login et le mot de passe de l’utilisateur en paramètre de la requête HTTP afin d’obtenir une URL comme celle-ci :

http://www.rest-books.com/services/users/1/books?user=bob&password=47a6daf6c9

Le mot de passe est envoyé haché pour 2 raisons :

1 – Cela permet de ne pas faire circuler le mot de passe clairement sur le réseau.
2 – Cela permet également à la base de données de ne pas stocker le mot de passe de l’utilisateur en clair.

: Seul l’utilisateur 1 peut accéder à ses livres.
: Le mot de passe ainsi haché peut être retrouvé dans certains cas (voir « Les failles du hashage »).

3- Les failles du hashage

Même si il est à priori impossible de retrouver le mot de passe à partir du hash, de nombreux dictionnaires de conversion existent sur internet faisant le rapprochement entre un hash et un mot de passe :

  1. http://md5-database.org/md5/
  2. http://md5-database.org/sha1/

Une solution de contournement existe cependant : l’utilisation d’un « salt » (grain de sel).
La technique consiste à hasher une première fois le mot de passe, à concaténer le hash à une chaîne de caractère (salt) et à le hasher une seconde fois. Le salt est unique et connu à la fois du serveur et de l’application cliente, il n’est pas caché au yeux du hacker. Son seul but est de rendre votre mot de passe indéchiffrable sur le réseau.

String salt = "48@!alsd";
String password = "toto";
String encryptedPassword = sha1(sha1(password)+salt);

Nb : Pour ma part, j’ai utilisé cette classe utilitaire pour hacher les mots de passes en Java (String ? SHA1) : « http://goo.gl/vNmP6« .

: Maintenant, les mots de passes sont vraiment indéchiffrables.
: Dans le cas où un hacker arrive à intercepter l’encryptedPassword, il aura tout le temps qu’il souhaite pour exécuter n’importe quelle requête en notre nom (voir « L’attaque de l’homme du milieu »).

4- Le Token d’authentification

L’idée du token d’authentification est simple. Celle-ci consiste à envoyer dans un 1er temps un couple login/password et de récupérer un token en retour. Dans un second temps, ce token devra être utilisé à la place du password. Ceci permet, coté serveur, de gérer la durée de validité des tokens (à 20 minutes par exemple).


: Après une 1ere étape de login, le mot de passe ne transite plus sur le réseau.
: Dans le cas où un hacker arrive à intercepter notre token d’authentification, il aura une vingtaine de minutes pour exécuter n’importe quelle requête en notre nom (voir « L’attaque de l’homme du milieu »).

5- L’attaque de l’homme du milieu

L’attaque de l’homme du milieu (ou « man in the middle attack » ou « MITM ») est une technique de hacking consistant, pour un hacker, à se positionner entre deux parties (ici : l’utilisateur et le serveur) et à lire et/ou modifier les échanges entre eux.

Dans cette configuration, si notre hacker arrive à obtenir notre token, celui-ci sera en mesure de se faire passer pour l’utilisateur pendant une vingtaine de minutes. Dans l’exemple ci-dessous, il sera par exemple capable de supprimer le compte de l’utilisateur dont il a récupéré le token d’authentification.

L’utilisation du token ne permet, du point de vue de l’utilisateur, que de se substituer au password. Il ne certifie en aucun cas que vous êtes l’émetteur de la requête. La technique consiste alors à remplacer notre token d’authentification par une signature sur la requête.

6- Signer ses requêtes

Le principe des signatures est d’ajouter un paramètre supplémentaire à notre requête afin de garantir au récepteur à la fois l’identité du rédacteur de la requête ainsi que l’intégrité de celle-ci. Avec cette signature, même si un hacker parvient à récupérer notre requête, la seule action qu’il sera en mesure de réaliser sera de rejouer cette même requête sans y changer aucun paramètre. Par conséquent, l’exemple de la suppression du compte utilisateur illustré dans l’exemple précédent n’est plus possible.

Le calcul de cette signature et sa vérification par le serveur est rendu possible grâce aux algorithmes de type HMAC.

Un HMAC, est un type de code d’authentification de message (CAM), ou MAC en anglais (Message Authentication Code), calculé en utilisant une fonction de hachage cryptographique en combinaison avec une clé secrète. Comme avec n’importe quel CAM, il peut être utilisé pour vérifier simultanément l’intégrité de données et l’authenticité d’un message. N’importe quelle fonction itérative de hachage, comme MD5 ou SHA-1, peut être utilisée dans le calcul d’un HMAC ; le nom de l’algorithme résultant est HMAC-MD5 ou HMAC-SHA-1. La qualité cryptographique du HMAC dépend de la qualité cryptographique de la fonction de hachage et de la taille et la qualité de la clé.
(Source : Wikipedia)

Grâce au HMAC, le hacker ne peut pas recréer votre signature car il ne connaît pas le mot de passe qui a servi à la générer.

: L’identité du rédacteur de la requête est certifiée (personne ne peut  créer une signature à votre place)
: Le serveur est sûr que le contenu de la requête n’a pas été modifié ou altéré.
: Si un hacker intercepte la requête HTTP, il sera en mesure de la rejouer.

Bien que cette technique puisse sembler suffisamment robuste, la limite liée à la ré-exécution des requêtes s’avère vite plus importante qu’il n’y parait. Imaginez une application bancaire gérant des demandes de virements : un hacker ré éxécutant des requête de débit sur un compte client aurait des conséquences désastreuses. De la même manière la redondance d’une requête peut exister dans des infrastructures réseau avancées. Notons également que dans certaines applications, un simple double-clic génère 2 appels au serveur avec les même conséquences.

C’est pour ces raisons que les signatures intègrent un paramètre supplémentaire capable d’aider le serveur à identifier les requêtes déjà exécutées.

7- Les requêtes à usage unique

Ce type de requête est utilisée par la plupart des fournisseurs d’API REST tels qu’Amazon Web Services. La technique est simple, il suffit d’ajouter le timestamp à la signature ainsi qu’aux paramètres de la requête lors de la génération de la requête REST. Ainsi, le serveur n’a qu’à stocker le timestamp de la dernière requête traitée et le comparer avec celui de la requête en cours. Si le timestamp de la requête courante est inférieur ou égal à celui du dernier timestamp enregistré, cela signifie que la requête à déjà été jouée. Sinon, cela signifie que la requête est jouée pour la 1ere fois.

7.1 – Coté client

1 – L’application cliente construit sa requête en intégrant dans les paramètres l’identifiant de l’utilisateur et le timestamp actuel.

2 – L’application cliente construit la signature de la requête à partir d’un algorithme du type :

url = GET:http://www.rest-books.com/services/users/1/books?user=bob&timestamp=1356444113
signature = Base64( HMAC-SHA1( UTF-8-Encoding-Of( url ), password ) );
url = url + "&" + signature;

L’application cliente envoie la requête en intégrant dans ses paramètres la signature. Par exemple :

http://www.rest-books.com/services/users/1/books?user=bob&timestamp=1356444113&signature=e77c0c5d68

Voici ce que pourrait donner le calcul de notre signature en javascript avec l’utilisation de la librairie crypto-js pour réaliser les calculs de SHA1 et de HMAC-SHA1.

<script type="text/javascript" src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/sha1.js"></script>
<script type="text/javascript" src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/hmac-sha1.js"></script>
<script type="text/javascript">
    var user = "bob";        // Récupéré depuis la page d'authentification.
    var password = "toto";   // Récupéré depuis la page d'authentification.
    const salt = "48@!alsd"; // Connu côté client dès le début.
    var encryptedPassword = CryptoJS.SHA1(CryptoJS.SHA1(password)+salt);
    var httpVerb = "GET";
    var currentTime = +new Date();
    var url = "http://www.rest-books.com/services/users/1/books?user=" + user + "&timestamp=" + currentTime;
    var httpUrl = httpVerb + ":" + url;
    var signature = CryptoJS.HmacSHA1(httpUrl,encryptedPassword).toString(CryptoJS.enc.Base64);
    url = url + "&signature=" + signature;
    alert("CALL : " + url);
</script>

7.2 – Coté serveur


4 – A la réception de la requête, le serveur récupère votre mot de passe dans sa base de données.


5 – Le serveur génère la signature que la requête devrait contenir à partir de votre mot de passe et de la requête elle même. Le serveur utilise le même algorithme de calcul de la signature que l’application cliente.


6 – Le serveur vérifie si la signature calculée est identique à celle provenant de la requête.
Si c’est le cas, le serveur passe à l’étape 7. Sinon, il retourne une erreur 401.


7 – Le serveur récupère en base le timestamp de la dernière requête traitée pour l’utilisateur 123.


8 – Le serveur compare le timestamp contenu dans la requête avec celui obtenu à l’étape 7.
Si celui de la requête est inférieur ou égal à celui obtenu à l’étape 7, cela signifie que la requête a déjà été exécutée par le passé. Si il est supérieur, alors c’est bien la 1ere fois que la requête est envoyée au serveur. Celle-ci peut être exécutée sans problème.

: L’identité du rédacteur de la requête est certifiée (personne ne peut créer une signature à votre place).
: Le serveur est sûr que le contenu de la requête n’a pas été modifié ou altéré.
: Le serveur à l’assurance que la requête est exécutée pour la 1ere fois.
: Même si votre requête ne peut être ni altérée ni rejouée, celle-ci peut être « vue » par un hacker. Dans la plupart des cas, ceci n’a que peu d’importance mais dans le cadre d’une application bancaire, il est n’est pas envisageable qu’une tiers personne puisse connaître les montants et les destinataires de vos virements.

8- HTTPS = HTTP + SSL

SSL (pour Secure Socket Layer) est un système permettant de sécuriser les échanges de données entre deux ordinateurs. Associés, le HTTP et le SSL donnent le HTTPS. Ce protocole est intégré à l’immense majorité des navigateurs web et permet de sécuriser les échanges entre l’utilisateur et le serveur.

En utilisant du HTTPS, vous pouvez être sûr que personne n’interceptera vos requêtes. Ce mécanisme vous assure la sécurité du transport et uniquement de celui-ci (pas de gestion d’authentification, d’unicité des requêtes, …).

L’article de Sébastien Sauvage sur le SSL est l’un des meilleurs articles que j’ai trouvé sur le sujet (très clair et en français).

9- Récapitulatif

Si je devais résumer les points précédents, voici ceux qu’il faut garder en tête pour authentifier et sécuriser vos appels REST.

La Signature : C’est la base des échanges authentifiés dans le cadre d’une architecture REST. Celle-ci permet au serveur de s’assurer de l’identité de l’émetteur de la requête ainsi que de l’intégrité de celle-ci.

Le Timestamp : L’intégration d’un timestamp dans vos paramètres de requête et dans votre signature vous permettra de vous assurer de l’unicité de la requête.

HTTPS : L’utilisation du HTTPS ne vous aidera pas pour authentifier vos requêtes mais uniquement pour les sécuriser. Ce protocole permettra à votre serveur de s’assurer que la requête qu’il reçoit est identique à celle qui a été envoyée par le client. En outre, il permet de rendre la requête illisible par les éventuels hackers qui récupéreraient cette requête sur internet.

Le SALT : Dans la mesure où votre mot de passe ne transitera jamais sur le réseau, cette technique vous sera utile pour sécuriser les mots de passes présents dans la base de données de votre serveur dans le cas où un hacker parviendrait à les récupérer. Si vous mettez en place ce système, pensez à jeter un oeil sur l’article de Sébastien Sauvage sur le sujet.

Webographie

  1. Protect a REST service using HMAC : http://www.smartjava.org/content/protect-rest-service-using-hmac-play-20
  2. Designing a Secure REST (Web) API without OAuth : http://www.thebuzzmedia.com/designing-a-secure-rest-api-without-oauth-authentication/
  3. How to implement RESTful authentication : http://blog.synopse.info/post/2011/05/24/How-to-implement-RESTful-authentication
  4. Principles for Standardized REST Authentication :  http://broadcast.oreilly.com/2009/12/principles-for-standardized-rest-authentication.html
  5. REST, un style d’architecture universel : http://www.figer.com/publications/REST.htm
  6. REST – Architecture Orientée Ressource :http://loicmathieu.free.fr/wordpress/index.php/informatique/rest-architecture-orientee-ressource/
  7. Exemple de l’API REST Zanox : http://wiki.zanox.com/en/RESTful_API_authentication
  8. Amazon – Signing and Authenticating REST Requests :http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
  9. Amazon – Authenticating REST Requests :http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RESTAuthentication.html?r=1535
  10. Crypto-JS : http://code.google.com/p/crypto-js/
  11. Explications sur le SSL : http://sebsauvage.net/comprendre/ssl/

(PS : Merci à @jhattat pour la relecture de dernière minute…)