Auth + update Web

master
Denis MIGDAL 10 months ago
parent 4256818f13
commit 05a823114f

@ -7,6 +7,169 @@ include_toc: true
[TOC]
**L'authentification** est le fait de **vérifier une déclaration d'identité**, généralement en vue d'autoriser l'accès à des ressources (e.g. un compte). Pour cela l'utilisateur apporte une **preuve de son identité** qui sera vérifiée par un tiers.
Dans le cadre de services distants, l'utilisateur envoie une preuve d'identité auprès du **fournisseur de service** (FdS) afin de pouvoir s'authentifier et d'accéder ensuite aux ressources. Il est ainsi nécessaire d'utiliser un **protocole d'authentification**, i.e. de définir la manière dont le client et le FdS vont échanger des messages afin de permettre l'authentification, tout en respectant des **propriétés de sécurités**.
L'authentification est généralement inclue dans une négotiation de paramètres de sécurités (cf TLS) qu'on appelle **handshake** (*poignée de main* en anglais). **2-way handshake** et **3-way handshake** font référence au nombre de messages nécessaires à l'authentification/négotiation.
Dans la suite de ce document, nous partirons du principe que la preuve d'identité est un mot de passe. Cependant, il est tout à fait possible d'utiliser n'importe quelle suite arbitraire de charactères voire d'octets, quelle que soit son origine (e.g. suite d'octets aléatoires).
# Password Authentication Protocol (PAP)
Le protocole d'authentification le plus simple (et le moins sécurisé) reste tout simplement d'envoyer le mot de passe au serveur qui informera le client du succès ou de l'échec de l'authentification. Ce protocole est donc un 2-way handshake.
C'est sur ce principe qu'est construit **HTTP Basic Access Authentication** (HTTP BA) dans lequel l'en-tête de la **requête HTTP** contient un champ `Authorization: Basic [credentials]` intégrant le login et mot de passe encodé en base 64.
Vous noterez que le FdS doit alors avoir connaissance du mot de passe afin de pouvoir le comparer à celui reçu. Ce qui implique de le stocker en clair dans une base de donnée. Une variante de ce protocole serait d'envoyer et de stocker un hash (potentiellement salé et poivré) du mot de passe au lieu de directement l'utiliser.
# TLS : Sécuriser les échanges
Lors du processus d'authentification, il est bien évidemment nécessaire de protéger l'authenticité, l'intégrité, la confidentialité, et la destination des messages. Certains protocoles d'authentifications peuvent assurer eux-mêmes ces propriétés de sécurités, d'autres ont besoin d'une communication sécurisée (généralement TLS).
TLS est un protocole de communication de couches 5 et 6 permettant de sécuriser une communication. Les paramètres de sécurités sont négotiés au début de la communication :
- la version de TLS à utiliser ;
- un secret partagé pour l'algorithme d'échange de clefs ;
- la **suite de chiffrement** (cipher suite) à utiliser (e.g. *ECDHE-RSA-AES128-GCM-SHA256*) constitué de :
- l'algorithme d'échange de clefs (e.g. ECDHE) ;
- l'algorithme d'authentification (e.g. authentification du serveur par signature RSA) ;
- l'algorithme de chiffrement par bloc (e.g. AES en mode GCM) ;
- l'algorithme d'authentification de messages (e.g. SHA256).
Depuis la version 1.3, TLS utilise un 3-way handshake :
- **CLIENT HELLO** : le client envoie des données nécessaires à l'échange de clefs et propose des suites de chiffrements supportés.
- **SERVER HELLO + SERVER FINISHED** : le serveur envoie son certificat, une signature, la suite de chiffrement choisie, et des données nécessaires à l'échange de clefs.
- **CLIENT FINISHED :** le client authentifie le serveur a partir de sa signature et de son certificat.
Il est ensuite possible d'échanger des messages de manière sécurisée grâce aux algorithmes de chiffrement par bloc et d'authentification de messages.
TLS permet d'authentifier le serveur, le client, ou les deux. Usuellement, le serveur est authentifié, seul, via une chaîne de certificats (certificate chain), ce afin d'éviter les attaques de type Man In The Middle (MITM). L'utilisateur pourra ensuite s'authentifier directement auprès du service distant, par dessus la comunication sécurisée ainsi établie.
# OTP et forward secrecy
Il arrive que pour diverses raisons, un système se retrouve vulnérable à un instant T :
- l'authentification du serveur a été compromise du fait d'une authorité de certification compromise, d'un faux certificat déposé dans la liste des certificats de confiances du client, ou de l'utilisateur décidant de faire confiance au certificat malgré des avertissements.
- TLS n'a pas été utilisé lors d'un des échanges (ou des paramètres de sécurités faibles ont été utilisés), potentiellement du fait d'un défaut de configuration ou de mises à jour.
- L'utilisateur a utilisé un client corrompu, ou s'est fait avoir par du phishing (e.g. typosquatting).
Il est alors important d'assurer les deux propriétés suivantes :
- **forward secrecy** : la vulnérabilité du système à un instant T ne compromet pas les échanges futurs.
- **backward secrecy** : la vulnérabilité du système à un instant T ne compromet pas les échanges passés.
Si TLS assure ces 2 propriétés pour les communications entre le client et le serveur, il ne peut cependant pas protéger les informations transmises en cas de vulnérabilités. En effet, si une authentification est effectuée alors que la communication (ou le serveur) est vulnérable, un attaquant pourrait alors lire les messages échangés et en extraire le mot de passe envoyé du client vers le serveur. En connaissant le mot de passe, l'attaquant serait alors en capacité de s'authentifier. De manière plus générale, on souhaite éviter les **attaques par rejeux**.
L'objectif est alors d'utiliser une sorte de mot de passe unique à chaque authentification, un **One Time Password** (OTP). On peut voir l'OTP comme le produit d'une fonction $OTP(secret, i)$ où $secret$ serait le mot de passe maître.
Il existe plusieurs types d'OTP :
- **asynchrone** où $i$ est incrémenté à chaque authentifications réussies. Il ne peut cependant pas être utilisé pour s'authentifier sur plusieurs serveurs différents.
- **synchrone** où $i$ est calculé en fonction du temps, il n'est alors valide que pour une période de temps donné. Une désynchronisation des horloges du client et du serveur peut alors empêcher toute authentification.
- **basé sur un challenge**, cf section suivante.
La fonction $OTP$ repose généralement sur une fonction non-inversible, e.g. un hash, une signature, voire un chiffrement symmétrique, qu'on nommera $H$ ou $Sign_{key}$ ou $Cipher_{key}$. Plusieurs architectures sont alors possibles :
1. $OTP(secret, i) = H(secret | i)$ ou $Sign_{secret}(i)$ ou $Cipher_{secret}(i)$.
1. Si $H$ est basé sur une fonction de hashage, le serveur connaît $secret$ et est capable de calculer l'OTP de son côté pour ensuite le comparer avec celui reçu.
2. Si $H$ est basé sur une signature, le serveur connaît la clé publique et est capable de vérifier la signature reçue ainsi que le contenu du message reçu.
3. Si $H$ est basé sur un chiffrement symmétrique, le serveur connaît $secret$ et est capable de déchiffrer le message reçu.
2. Une chaîne de hash (**hash chain**) :
$OTP(secret, i-1) = H( OTP(secret, i) )$, et $OTP(secret, n) = secret$.
Reformulé autrement, $OTP(secret, i)$ correspond au secret hashé $n-i$ fois.
À la i-ème tentative d'authentification, le serveur reçoit $OTP(secret, i)$. Connaissant $OTP(secret, i-1)$, qui est aussi $H(OTP(secret, i))$, il lui suffit de calculer le hash l'OTP reçu puis de le comparer à l'OTP stocké. Si l'authentification réussie, il stocke l'OTP reçu pour la prochaine authentification.
L'inconvéniant de cette architecture est dans le calcul initial de $OTP(secret, 0)$, qui correspondra au $secret$ hashé $n - 0 = n$ fois. Par construction, il ne sera ainsi pas possible d'effectuer plus de $n$ authentifications. En effet, cela necéssiterait alors pour le client de pouvoir calculer $OTP(secret, n+1) = H^{-1}(OTP(secret, n)) = H^{-1}(secret)$. Or une fonction de hashage est, par construction, non-inversible.
3. De manière analague, il est aussi possible de construire une **chaîne de signature** :
$OTP(secret, i + 1) = Sign_{secret}(OTP(secret, i))$ et $OTP(secret, 0) = Sign_{secret}( H(secret) )$.
# Challenge-Response Protocols (CRP)
Pour des raisons évidentes, vous noterez qu'on évite le nom de *Challenge-Response Authentification Protocol*.
Lors d'une authentification, le serveur n'a en réalité pas besoin de connaître le mot de passe, mais de vérifier la connaissance du mot de passe par le client. Le principe est alors d'envoyer un challenge au client, auquel il ne peut correctement répondre que s'il connaît le secret. Il s'agit alors d'un 3-way handshake.
Un protocole d'authentification challenge-réponse ne dévoilant aucune information au serveur est nommé **0-knowledge authentication protocol** (preuve à divulgation nulle de connaissance), ou **0-Knowledge Interactive proof** (ZKIP) **based authentication**. Tous les CRP ne sont pas nécessairement des ZKIP.
Une manière d'implémenter cela est d'utiliser un $OTP(secret, i)$ basé sur un challenge, où $i$ serait alors un challenge généré aléatoirement à chaque authentification.
Cela est par exemple le cas de **Challenge Handshake Authentication Protocol** (CHAP), où $OTP(secret, i) = H(secret|i)$ avec $i$ un nombre aléatoire généré par le serveur et envoyé au client.
Ce protocole reste toutefois vulnérable à plusieurs attaques. **HTTP Digest Access Authentication** (HTTP DAA) implémente plusieurs protections additionnelles.
`Authorization: Digest username:realm:nonce:uri:response:qop:cnonce:nc` est ainsi calculé à partir d'informations additionnelles :
- **realm**, un texte affiché à l'utilisateur pour informations. Il contient généralement le domaine du serveur sous la forme `group@domain.com`, évitant ainsi théoriquement les **attaques par relais, phishing et/ou typosquatting**.
- le serveur stocke les **server nonce** (nonce), i.e. les challenges, récemment émis, qui peuvent aussi contenir un timestamp afin d'éviter les **attaques par rejeux** ainsi qu'une réutilisation d'un nonce.
- un **client nonce** (cnonce), i.e. une chaîne de caractères aléatoire choisi par le client afin d'éviter des **attaques par** **textes clairs choisis**.
- un **nonce count** (nc) comptant le nombre de fois qu'un nonce a été utilisé. Par exemple lorsque l'utilisateur se trompe dans la saisie de son mot de passe et rééssaye de s'authentifier.
- la ressource demandée (`HA2`), permettant de lier l'authentification à la ressource demandée afin de garantir l'intégrité de la demande. En effet, lors d'une authentification, un attaquant pourrait modifier un message, sans toucher la réponse au challenge, e.g. modifier le montant d'une transaction bancaire.
- un hash du mot de passe est utilisé (`HA1`), évitant au serveur d'avoir à connaître et donc à stocker le mot de passe en clair.
`response` est alors calculé ainsi :
```
HA1 = H(username:realm:password)
HA2 = H(method:URI:H(body))
response = H(HA1:nonce:nc:cnonce:qop:HA2)
```
HTTP DAA souffre cependant de plusieurs vulnérabilités :
- Il ne permet pas de vérifier l'authenticité du serveur, et requiert donc d'être utilisé par-dessus TLS.
- le serveur peut demander une authentification HTTP BA.
- si la base de donnée du serveur fuite, un attaquant peut alors utiliser les `HA1` stockés pour s'authentifier.
# La sécurité d'un protocole d'authentification
Garantir l'authenticité d'un serveur ne suffit pas à garantir son honnêteté et intégrité.
Un serveur malhonnête peut tout à fait prendre des libertés avec le protocole d'authentification en :
- forçant des paramètres de sécurités faibles ;
- ne générant pas aléatoirement les nonces ;
- récupérant le challenge d'un autre serveur afin de construire le challenge envoyé au client ;
- en envoyant des messages sans respecter l'ordre, le format, ou les données définis par le protocole ;
- collaborant avec d'autres serveurs ;
- etc.
Si certaines contre-mesures sont simples, comme n'envoyer que le mot de passe hashé, de poivrer ce hash avec le nom de domaine du serveur, etc. certaines attaques peuvent se révéler bien plus complexes à prendre en considération.
Garantir qu'un protocole d'authentification est fiable n'est pas chose aisée. Il est facile de montrer qu'une attaque/vulnérabilité donnée existe au sein d'un protocole donné, mais prouver l'absence de vulnérabilités est impossible car nécessiterait d'avoir conscience de toutes les attaques possibles.
En général, on établi des **preuves de sécurité** à partir d'un **modèle d'attaque/attaquant**, i.e. on défini l'attaquant et ses capacités (actions autorisées, puissance de calcul, connaissances de certains secrets, etc.). Pour cela on utilise les **méthodes formelles**, qui permettent de prouver mathématiquement qu'un secret ne peut être connu par un attaquant, pour un modèle d'attaque et protocole donnés.
Cependant, même avec des preuves de sécurité, le protocole peut être vulnérable face à un attaquant ayant des capacités supérieures à celles définies par le modèle d'attaque. Par exemple, si l'attaquant a la capacité de casser TLS ou de collaborer avec un tiers, certaines propriétés de sécurités de certains protocoles peuvent se retrouver menacés. On évalue donc la sécurité d'un système d'authentification relativement au(x) modèle(s) d'attaque(s) contre lesquels on souhaite se protéger.
Un protocole sécurisé peut aussi être vulnérable dans son implémentation, que ce soit lié à une ambiguité dans le protocole, à une erreur de programmation, à une vulnérabilité dans une bibliothèque utilisée, dans le système d'exploitation, voire même dans le matériel utilisé (RAM, CPU, etc).

@ -0,0 +1,87 @@
---
gitea: none
include_toc: true
---
[TOC]
Un même utilisateur peut s'authentifier auprès de plusieurs services différents, parfois appartenant à la même organisation. Par exemple, au sein de l'UCA, vous utilisez différents services :
- l'ENT ;
- Moodle ;
- l'emploi du temps ;
- les mails ;
- l'application de suivi des stages ;
- le suivi des notes ;
- le suivi des absences ;
- etc.
Si vous deviez avoir un compte différent pour chacun de ces services, cela deviendrait très vite ingérable avec des problèmes de synchronisations. Imaginez donc avoir à retenir au moins 7 mots de passes différents rien que pour l'UCA, ou d'avoir à changer une information sur vos comptes avec le risque d'en oublier un.
Pour résoudre ce problème on utilise des **fournisseurs d'identités** (Identity Provider, IP/IdP). L'IP est l'entité qui gérera votre compte, i.e. l'authentification et les informations liées à votre compte.
On distingue généralement 4 architectures :
- **une gestion isolée** : le cas le plus simple où le SP gère lui-même votre compte.
- **une gestion centralisée** : un IP gère votre compte pour plusieurs SP.
- **une gestion centrée utilisateur** : vous hébergez vous-même votre propre IP localement.
- **une gestion fédérée** : un ensemble d'IP permettent de se connecter à plusieurs SP.
Pour des raisons de sécurité, on part généralement du principe que les différents acteurs (ici le client, l'IP, et le SP) ne se font pas confiance et peuvent être soit malhonnêtes, soit corrompus.
# La sécurité des données stockée
-> IP stocke données et transmets aux SP. -> auth. SP vérifier s'il a accès aux données.
-> SP -> IP ==> soit stocké localement (pas partagés), soit chiffré sym. (conf.) potentiellement avec une clé partagée avec d'autres SP (ou sur de la crypto multi-parties - e.g. chiffrer les clés symétriques), soit signé/hashé pour garantir l'intégrité.
Idem client vs SP données (e.g. stockage/partage de données) : dérivé du MdP clé privée, pour chiffrer une clé asym permettant de stocker des données chiffrées, déchiffrée sur le client.
# Jetons d'accès (token access)
Preuve auth. IP => SP, passe par le client (éviter preuve passe par SP).
+ SP => IP pour accès aux données
- pas confiance IP
- unlikability.
# Futur
- 7 lois d'ID
- SSO
- Unlikability

@ -11,12 +11,12 @@ include_toc: true
## S3 IDN
1. Acteurs sur Internet
1. Acteurs sur Internet (cf PDF)
2. Bases du réseau
3. Biométrie
4. Biométrie attaques et protections
5. [Authentification](./IDN/5-Authentification.md)
6. Systèmes d'identités
4. Biométrie, limites, attaques et protections
5. [Protocoles d'authentification](./IDN/5-Authentification.md)
6. [Systèmes d'identités](./IDN/6-Systèmes-Identités.md)
# Web

@ -142,7 +142,7 @@ Pour du contenu dynamique, i.e. généré sur demande par le serveur, il existe
- *implémenter son propre serveur* qui répondra aux requêtes des clients. Le serveur peut alors supporter plusieurs protocoles en sus de HTTP, e.g. des WebSockets, des sockets TCP, etc. Il peut être implémenté dans n'importe quel langage (e.g. Python, JavaScript, shell), et a donc accès à toutes leurs fonctionnalités et bibliothèques.
- *rediriger le flux* vers un autre serveur (reverse proxy). Quasi-identique à la solution précédente, il a l'avantage de pouvoir bénéficier des fonctionnalités du serveur Web (authentification, cache, etc), ainsi que d'éviter des problèmes de *Same Origin Policy* (SOP).
- *rediriger le flux* vers un autre serveur (reverse proxy). Quasi-identique à la solution précédente, il a l'avantage de pouvoir bénéficier des fonctionnalités du serveur Web (authentification, cache, etc), ainsi que d'éviter des problèmes de *Same Origin Policy* (SOP). Il peut aussi permettre de simplifier l'implémentation du serveur, en déléguant la sécurisation de la communication au reverse proxy, i.e. le serveur envoie et reçoit des données en clair, et le reverse proxy les chiffre et les déchiffre.
Le contenu généré peut être sous n'importe quel format : de l'HTML, des images, du texte brut, des données binaires, du JSON, etc. Cependant, il est préférable de séparer la structure de la page Web (HTML/CSS/JS/Images, etc.), des données dynamiques du site Web, cela pour plusieurs raisons :
@ -152,24 +152,16 @@ Le contenu généré peut être sous n'importe quel format : de l'HTML, des imag
- *découpler la structure de la page Web des données*, permettant ainsi de formatter les données afin de pouvoir facilement les réutiliser pour d'autres usages tout en évitant qu'une modification de la structure de la page Web ne casse tout.
**⚠** Vous ne devez **<u>*JAMAIS*</u>** faire confiance client. En effet, il est très aisé d'envoyer des données arbitraires au serveur. Vous devez ainsi *<u>**SYSTÉMATIQUEMENT**</u>* vérifier la validité des données envoyées par le client (format, valeurs, autorisations, etc).
## Serveur HTTP
Un serveur HTTP répond aux *requêtes HTTP* du client par une *réponse HTTP*. Les requêtes et réponses HTTP se composent d'un *en-tête* (header) contenant les méta-données, et d'un *corps* (body) contenant les données échangées.
L'en-tête de la requête contient ainsi l'URI demandée et la *méthode HTTP*. L'en-tête de la réponse contient le *code de status* de la réponse (200 si tout c'est bien passé). Cf la section *"Envoyer une requête HTTP"* de la fiche *API JS/DOM* pour plus de détails.
Côté serveur HTTP, on manipule des routes. Une route représente un ensemble d'URI qui seront traitées par le même gestionnaire. Il peut contenir des paramètres e.g. `/dir/$PARAM/foo` qui seront exploités par le gestionnaire (handler).
Afin de rendre le code plus lisible, il est fréquent que les frameworks représentent les routes par une arborescence de fichiers. Au démarrage, le framework va ainsi lire de manière récursive un dossier e.g. `/routes/` et ajouter les différents gestionnaires en fonction des fichiers qui s'y trouvent. Ainsi, le fichiers/dossier `/routes/dir/{PARAMS}/foo` contiendra le gestionnaire à utiliser pour la route `/dir/$PARAM/foo`.
🚩 [TODO] : outils network
@ -235,8 +227,6 @@ const port = Number(Deno.env.get("PORT")) || 3000;
app.listen(port, () => {
console.log(`Listening on http://localhost:${port}...`);
});
```
```python
@ -272,7 +262,7 @@ async def myhandler(request):
print(body)
return web.Response(text=$DATA)
# Renvoyer du JSON dans la réponse
@routes.get($ROUTE)
async def myhandler(request):
@ -306,17 +296,10 @@ cors = aiohttp_cors.setup(app, defaults={
if __name__ == '__main__':
web.run_app(app)
```
### Serveur HTTP (avec arborescence de routes)
## API REST
Une API REST est une manière de concevoir les échanges HTTP entre le client et le serveur de sorte à les uniformaliser, les rendre plus compréhensibles, et faciliter les opérations de tests et de déboguages. Une API REST est donc implémentée par un serveur HTTP.
@ -335,13 +318,15 @@ Une API REST supporte 5 méthodes HTTP :
- POST : ajouter une ressource à une collection.
- *PUT :* modifier entièrement une ressource ou la créer si elle n'existe pas.
- *PUT :* créer une ressource ou la remplacer si elle existe.
- *PATCH :* modifier partiellement une ressource.
- *DELETE :* supprimer une ressource.
L'URI/route des ressources doivent suivre le même format. On appelle *collection* une ressource qui est elle-même un ensemble de ressources. La modification d'une ressource est effectuée de la sorte :
L'URI/route des ressources doivent suivre le même format. On appelle *collection* une ressource qui est elle-même un ensemble de ressources. Les routes permettent aussi de gérer plus facilement les droits d'accès aux données, en autorisant/interdisant l'accès à certaines routes, soit au niveau du serveur REST, soit via un reverse Proxy se plaçant entre le client et le serveur REST (e.g. apache, nginx).
La modification d'une ressource est effectuée de la sorte :
- `GET /$COLLECTION/` : obtenir la liste des ressources de la collection.

Loading…
Cancel
Save