====== Créer votre propre authentification double-facteur avec TOTP ====== L'authentification d'un utilisateur se fait habituellement avec login et mot de passe. On peut améliorer la sécurité en ajoutant des facteurs. On en compte habituellement 3: * Ce que je **sais** (Mon mot de passe). * Ce que je **possède** (un object, une clé, un token RSA...). * Ce que je **suis** (biométrie). Si la biométrie nécessite souvent du matériel spécialisé (lecteur d'empreinte digitale, iris, rétinienne...), on peut facilement se créer l'équivalent d'un token d'authentification avec son smartphone (//ce que je possède//). Il existe des applications respectant un protocole standardisé pour cela: OTP (One Time Password). L'intérêt de l'OTP est que vous n'avez pas besoin de connexion entre votre client OTP (votre smartphone) et le serveur. Ils peuvent générer et contrôler la validité des OTP de manière déconnectée. Ce système peut donc fonctionner même si vous n'avez pas de réseau GSM à portée. Ce protocole permet de créer un code qui ne sera utilisable qu'une seule fois. Nous allons juste ajouter un champ de saisie dans le formulaire de login. Vous allez voir, ce n'est vraiment pas compliqué. Côté serveur, nous allons utiliser [[https://github.com/lelag/otphp|otphp]].\\ Côté client, [[https://fedorahosted.org/freeotp/|FreeOTP]] (disponible sous Android et iOS). ===== Quel type d'OTP ? ===== Il existe deux type d'OTP: HOTP et TOTP. **HOTP** est basé sur un compteur. A chaque utilisation, le compteur est incrémenté côté client et côté serveur. L'inconvénient est que s'il y a un raté, il faut re-synchroniser les compteurs. **TOTP** est basé sur la date et l'heure. A partir du moment où le client et le serveur sont synchronisés, on peut générer autant d'OTP qu'on le souhaite. Ces OTP ont une durée de validité limitée (qu'on peut paramétrer). Nous allons implémenter TOTP. ===== Premier test ===== ==== Installer et configurer la partie client (FreeOTP) ==== Installez FreeOTP sur votre smartphone ([[https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp|pour Android]], [[https://itunes.apple.com/us/app/freeotp/id872559395|pour iOS]]) et lancez-le Tout en haut à droite, appuyez sur la petite clé avec le signe '+' pour ajouter un OTP. Il va vous proposer de scanner un QR-Code. Nous n'en avons pas: Choisissez "Manual entry". Remplissez les champs: * **Issuer**: //toto// (Mettez-y votre nom, votre email ou le nom de votre société.) * **ID**: //1//. (Vous pourrez mettre d'autres valeur si vous voulez avoir plusieurs OTP différents pour les différencier.) * **Secret**: "//DEKGGLZJ//" (pour l'exemple). C'est le secret qui sera partagé par le client et le serveur. //Personne ne doit le connaître !// Il doit être aléatoire, et il faut l'entrer en base32. La base32 n'est pas très courante, mais vous pouvez entrer des caractères aléatoires (A-Z 2-7): Dès que le bouton "Add" n'est plus grisé, c'est que votre valeur est valide base32. Notez cette valeur, vous devrez la recopier dans la partie serveur. * **Type**: //Time-based (TOTP)// * **Algorithm**: Laissez "//SHA1//" * **Interval**: //60//. C'est la durée de validité du code OTP en secondes (Nous y reviendrons). * **Digits**: Laissez //6//. * Valider sur "//Add//". Vous devez maintenant voir un rectangle blanc avec des tirets. Appuyez dessus: Un code à 6 chiffre s'affiche, avec un compte-à-rebours à droite: Il vous indique la durée de vie restante de ce code. Maintenant installons la partie serveur. ==== Récupérer et installer la librairie côté serveur ==== Récupérez le [[https://github.com/lelag/otphp/archive/master.zip|zip de la librairie otphp sur GitHub]] et décompressez-le. Récupérez seulement les répertoire ''lib'' et ''vendor'' et placez-les dans un sous-répertoire ''otphp''. Vous devez donc obtenir: index.php (votre application) \otphp \lib hotp.php otp.php otphp.php totp.php \vendor base32.php libs.php ==== Vérifier et synchroniser l'heure du serveur ==== Si votre serveur et votre téléphone ne sont pas synchrones au niveau de l'heure, les codes TOTP seront valides moins longtemps que prévu, voir même pas du tout. Affichez l'heure de votre serveur, et ajustez au besoin la valeur ''$decalage'' afin que l'heure obtenue soit exactement celle de votre téléphone (idéalement à la seconde près). ==== Tester la génération d'un TOTP ==== Maintenant que les heures sont synchro, ajoutons la génération de l'OTP et affichons-le: '; require_once dirname(__FILE__).'/otphp/lib/otphp.php'; $totp = new \OTPHP\TOTP("DEKGGLZJ",array('interval'=>60)); echo $totp->at($maintenant); ?> Remarquez que nous avons remis les mêmes paramètres: * Le secret commun (''DEKGGLZJ'') * L'interval à 60 secondes ('''interval'=>60'') * Par défaut, l'algo est SHA1 et le code sur 6 chiffres (Donc inutile de le préciser). Il devrait vous afficher quelque chose du genre: 2014-07-11 22:53:41 812059 Si l'heure affichée correspond bien à l'heure de votre smartphone, la seconde ligne devrait vous afficher un code identique à celui que vous affiche l'application FreeOTP. Cette valeur n'est pas valide plus de 60 secondes. Si ce n'est pas le cas, c'est un manque de bol (vous avez généré un code juste à la limite de la période de temps), ou alors vous avez fait une erreur sur l'un de ces trois paramètres: * Heure exacte entre serveur et smartphone. * Secret commun * Interval En cas de doute, supprimez l'OTP dans l'application FreeOTP et re-crééez-en une nouvelle. ===== Sécuriser un formulaire de login ===== Maintenant que le codes OTP sont générés à l'identique entre serveur et smartphone, nous allons nous en servir pour sécuriser un formulaire de login existant. ==== Notre application ==== Nous allons créer une application fictive qui n'a qu'un seul écran (l'écran de login) et capable de faire une seule chose: Vous dire si votre login et mot de passe sont corrects.
Login:
Mot de passe:


Vous noterez que notre application est particulièrement stupide: Login et mot de passe sont codés en dur dans le programme. Mais peu importe, c'est suffisant pour la démonstration. Si vous entrez bien toto/titi, elle vous répondre: ''Login OK !''. {{:totp:totp_01.png?nolink|}} ==== Modification du formulaire de login ==== Nous allons maintenant ajouter l'OTP: Ajoutons d'abord un champ OTP au formulaire de login. OTP:
Le formulaire devient:
Login:
Mot de passe:
OTP:

==== Modification du traitement du formulaire ==== Nous allons ajouter la fonction suivante: // Vérifie la valeur OTP function checkOTP($otp) { $decalage = 0; // (en secondes) $maintenant = time() + $decalage; require_once dirname(__FILE__).'/otphp/lib/otphp.php'; $totp = new \OTPHP\TOTP("DEKGGLZJ",array('interval'=>60)); return $totp->verify($otp,$maintenant); } Cette fonction renvoie ''true'' si le code OTP est valide. Ajoutons maintenant la vérification de l'OTP à celle du login/mot de passe: if ( checkLoginPassword($_POST['login'], $_POST['password'] ) && checkOTP($_POST['otp']) ) Le code entier devient donc:
Login:
Mot de passe:
OTP:


60)); return $totp->verify($otp,$maintenant); } // Traitement du formulaire de login: if (!empty($_POST['login'])) { if ( checkLoginPassword($_POST['login'], $_POST['password'] ) && checkOTP($_POST['otp']) ) echo "Login OK !"; else echo "Echec login"; } ?>
==== Test ==== Voici donc notre nouveau formulaire: {{:totp:totp_02.png?nolink|}} Entrez login, mot de passe, puis tapez l'application FreeOTP pour qu'elle vous génère un code. Entrez ce code dans le champ 'OTP' et validez: Votre login devrait être accepté. Vous pouvez refaire le test avec le **bon** login, le **bon** mot de passe mais un code OTP 6 chiffres au hasard: Votre login sera refusé même si le mot de passe est bon. C'est tout l'intérêt d'OTP: Si une personne vous vole votre mot de passe, elle ne pourra pas l'utiliser pour se connecter car elle n'a pas le générateur OTP de votre smartphone. Elle n'a pu voler qu'un seul des deux facteurs (//ce que je sais//). Cela est utile si le mot de passe est volé sur le réseau, ou sur la machine (keylogger) ou si quelqu'un vous voit le taper. ===== Conseils ===== * Le code OTP ne doit //jamais// remplacer le mot de passe. Il ne peut que le compléter. * Bien entendu, on ne laisse jamais les mots de passe et secrets OTP directement dans les sources. Stockez-les ailleurs ! * Je vous recommande de toujours afficher la date de l'heure sur votre formulaire de login. Vous pourrez ainsi immédiatement voir s'il y a un décalage avec votre téléphone. * Il est facile de mettre la vérification OTP sous forme d'une librairie bien à part que vous pourrez appeler depuis différentes applications php. La double authentification sera alors facile à ajouter à la plupart des applications php existantes: * Simple ajout d'un champ ''OTP'' dans le formulaire de login. * Un ''require_once'' pour inclure votre librairie. * Appel à votre fonction ''checkOTP()'' là où le logiciel vérifie login et mot de passe. ===== Exemple ===== ==== DokuWiki ==== Voici en exemple comment sécuriser le formulaire de login de DokuWiki avec TOTP. La bonne manière de faire aurait été de créer un plugin d'authentification spécifique, mais j'ai choisi de taper directement dans le code (ce qui obligera à refaire la manip à la prochaine mise à jour, mais c'est assez rapide à faire: 6 lignes à modifier). Dans ''inc/html.php'': --- html.php.original Mon Jul 14 13:49:34 2014 +++ html.php Mon Jul 14 19:51:35 2014 @@ -47,6 +47,7 @@ $form->addHidden('do', 'login'); $form->addElement(form_makeTextField('u', ((!$INPUT->bool('http_credentials')) ? $INPUT->str('u') : ''), $lang['user'], 'focus__this', 'block')); $form->addElement(form_makePasswordField('p', $lang['pass'], '', 'block')); + $form->addElement(form_makePasswordField('otp', 'OTP', '', 'block')); if($conf['rememberme']) { $form->addElement(form_makeCheckboxField('r', '1', $lang['remember'], 'remember__me', 'simple')); } Dans ''inc/auth.php'': --- auth.php.original Mon Jul 14 13:49:33 2014 +++ auth.php Mon Jul 14 20:14:55 2014 @@ -110,6 +110,7 @@ $evdata = array( 'user' => $INPUT->str('u'), 'password' => $INPUT->str('p'), + 'otp' => $INPUT->str('otp'), 'sticky' => $INPUT->bool('r'), 'silent' => $INPUT->bool('http_credentials') ); @@ -179,6 +180,7 @@ return auth_login( $evdata['user'], $evdata['password'], + $evdata['otp'], $evdata['sticky'], $evdata['silent'] ); @@ -213,7 +215,7 @@ * @param bool $silent Don't show error on bad auth * @return bool true on successful auth */ -function auth_login($user, $pass, $sticky = false, $silent = false) { +function auth_login($user, $pass, $otp, $sticky = false, $silent = false) { global $USERINFO; global $conf; global $lang; @@ -228,7 +230,8 @@ if(!empty($user)) { //usual login - if($auth->checkPass($user, $pass)) { + require_once realpath(dirname(__FILE__).'/../../myotp/myotp.php'); // Lien vers votre librairie OTP + if($auth->checkPass($user, $pass) && checkOTP($otp)) { // make logininfo globally available $INPUT->server->set('REMOTE_USER', $user); $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session ==== Codiad ==== Dans ''index.php'': --- index.php.original Sun Jul 13 07:56:04 2014 +++ index.php Tue Jul 15 14:21:30 2014 @@ -125,6 +125,9 @@ + + +