# Protection en 2-man-rule

Lors de l'étape précédente, nous avions abouti à une version satisfaisante d'un point de vue sécurité.

Néanmoins, cette version a toujours un défaut d'expérience utilisateur : si l'utilisateur perd son mot de passe, il ne peut plus récupérer son identité Seald, et perd donc la capacité de déchiffrer ses données.

Lors de cette étape, nous remplacerons la méthode de protection de l'identité par mot de passe par la méthode de protection en 2-man rule. Cette méthode nécessite l'envoi d'un challenge à l'utilisateur. Dans un premier temps, ce challenge sera envoyé par email, puis nous verrons comment l'envoyer par SMS.

La branche sur laquelle est basée cette étape est 4-signup-jwt (opens new window) , le résultat avec le challenge par email est 5-two-man-rule (opens new window) . L'implémentation avec le challenge par SMS est 6-two-man-rule-sms (opens new window).

# Explication

Tel qu'expliqué dans le guide dédié, l'objectif du mode de protection en 2-man-rule est de donner aux utilisateurs "le droit" d'oublier leur mot de passe. La contrepartie est que ce mode n'est pas strictement de bout-en-bout.

Son fonctionnement consiste à "couper" en deux l'identité, et à placer une moitié — appelée twoManRuleKey — sur le backend soumis à authentification, et l'autre moitié — appelée encryptedIdentity — sur Seald SDK Key Storage (SSKS) soumis à une authentification indépendante par email de confirmation.

Pour implémenter la protection en 2-man-rule il faut :

  • modifier le backend pour :
    • stocker une twoManRuleKey pour chaque utilisateur en base de données  ;
    • exposer un point d'API authentifié pour générer, rengistrer et récupérer la twoManRuleKey, et pour que le frontend puisse interagir avec SSKS afin de déposer & récupérer la deuxième moitié de la clé encryptedIdentity ;
  • modifier le frontend pour :
    • remplacer le plugin de protection par mot de passe par le plugin @seald-io/sdk-plugin-ssks-2mr de protection en 2-man-rule ;
    • gérer l'étape d'authentification indépendante par SSKS.

TIP

Pour une exécution plus rapide, vous pouvez utiliser une rawTwoManRuleKey à la place de la twoManRuleKey. Pour plus de détails, allez voir le paragraphe "Utilisation d'une TwoManRule Key brute" du guide sur les identités.

# Modification du backend

Nous allons dans l'ordre :

  • modifier le modèle User pour pouvoir stocker une twoManRuleKey pour chaque utilisateur en base de données ;
  • ajouter des variables dans le fichier de configuration pour authentifier la connexion du backend à SSKS ;
  • exposer un point d'API sendChallenge2MR permettant de générer / récupérer la twoManRuleKey et de créer une session entre le frontend et SSKS afin d'y déposer /récupérer la deuxième moitié de la clé encryptedIdentity.

# Modification du modèle User

Dans le fichier backend/models.js, on ajoute à l'initialisation du modèle User la propriété twoManRuleKey :

# Fichier de configuration

On ajoute deux variables dans le fichier de configuration du backend :

Nom Valeur par défaut Obligatoire Description
KEY_STORAGE_URL undefined Oui URL de SSKS, disponible sur le tableau d'administration Seald
KEY_STORAGE_URL_APP_KEY undefined Oui Clé d'API ssksAppKey, disponible sur le tableau d'administration Seald

Pour savoir où trouver ces valeurs, référez-vous au paragraphe en question dans le guide concernant l'intégration du 2-man-rule.

# Point d'API sendChallenge2MR

Ce point d'API fait en une requête les deux éléments décrits dans le guide d'intégration dédié :

  • il génère & stocke si besoin, puis rend la twoManRuleKey associée à un utilisateur de votre application ;
  • il utilise l'API de SSKS pour que l'utilisateur puisse s'authentifier auprès de SSKS.

Dans le fichier backend/routes/account.js, on va ajouter une route POST authentifiée '/sendChallenge2MR'.

Pour interagir avec SSKS on va utiliser node-fetch, il faut donc l'installer :

cd backend
npm install node-fetch

Puis l'importer dans le fichier backend/routes/account.js :

Enfin, on crée la route /sendChallenge2MR qui va :

  • ordonner à SSKS, si une authentification de l'utilisateur est nécessaire, d'envoyer un email au format du template à l'adresse email de l'utilisateur qui contiendra un challenge (valable 6h) que l'utilisateur renverra à SSKS dans la session identifiée par le twoManRuleSessionId ;
  • générer, stocker & rendre la twoManRuleKey à l'utilisateur ;
  • indiquer au frontend si une authentification est nécessaire pour stocker l'identité sur SSKS.

TIP

Il est important de donner un numéro de téléphone ou une adresse e-mail valide, ainsi qu'un template contenant $$CHALLENGE$$ de façon visible, étant donné que SSKS va remplacer $$CHALLENGE$$ par un challenge aléatoire que l'utilisateur devra recopier.

Désormais, nous avons fait toutes les modifications qu'il fallait effectuer sur le backend, passons au frontend.

# Modification du frontend

Nous allons dans l'ordre :

  • modifier le client API frontend/src/services/api.js pour y intégrer ce nouveau point d'API, et supprimer la pré-dérivation du mot de passe ajoutée précédemment, inutile avec le mode de protection en 2-man-rule ;
  • modifier le service Seald frontend/src/services/seald.js pour modifier les fonctions de création et de récupération d'identité ;
  • ajouter, si demandé par le backend (si mustAuthenticate === true), l'étape d'authentification par email effectuée par SSKS en UI dans le SignIn.jsx et le SignUp.jsx.

# Client API

Dans le fichier frontend/src/services/api.js, on ajoute la route sendChallenge2MR: body => POST('/account/sendChallenge2MR', body) à l'APIClient, puis on ajoute une méthode statique sendChallenge2MR à la classe User :

Ensuite, on peut supprimer la pré-dérivation du mot de passe dans les méthodes createAccount et login.

# Service Seald

# Configuration

On commence par installer le plugin @seald-io/sdk-plugin-ssks-2mr, supprimer le plugin @seald-io/sdk-plugin-ssks-password et scyrpt :

cd frontend
npm install @seald-io/sdk-plugin-ssks-2mr

npm uninstall @seald-io/sdk-plugin-ssks-password scrypt

Puis, dans le fichier frontend/src/services/seald.js, on l'importe et l'ajoute au SDK. On importe également User dont on aura besoin par la suite :

# Création et sauvegarde de l'identité

La sauvegarde de l'identité se fait désormais en deux étapes :

  • la demande d'envoi du challenge ;
  • la sauvegarde de l'identité sur SSKS, avec ou sans challenge selon la valeur de mustAuthenticate.

Pour cela, on modifie la fonction createIdentity qui n'a plus besoin du password comme argument, et qui, au lieu de sauvegarder l'identité par mot de passe, demande l'envoi d'un challenge via le point d'API créé ci-dessus :

Cette fonction retourne un objet contenant twoManRuleKey, twoManRuleSessionId (pour éviter toute confusion avec le sessionID de l'authentification du client sur le backend), et mustAuthenticate, envoyés par le backend.

Si mustAuthenticate est true, SSKS déclenche l'envoi d'un challenge par email ou par SMS. Ce challenge est valable 6h qui devra être recopié par l'utilisateur.

Si mustAuthenticate est false, la sauvegarde de l'identité sur SSKS peut se faire sans challenge.

On crée une fonction saveIdentity2MR qui permettra d'enregistrer l'identité sur SSKS :

Une fois cette fonction appelée, l'identité sera sauvegardée sur SSKS.

# Récupération de l'identité

Lors de la récupération d'une identité, l'authentification auprès de SSKS est obligatoire.

La récupération de l'identité se fait désormais en deux étapes :

  • l'envoi du challenge ;
  • l'utilisation du challenge pour récupérer l'identité depuis SSKS.

Tout d'abord on ajoute une fonction sendChallenge2MR qui fait juste un simple appel au point d'API précédemment créé sur le backend :

Ensuite, on modifie fonction retrieveIdentity en retrieveIdentity2MR qui ne prend plus password en argument, mais prend comme nouveaux arguemtns :

  • emailAddress : adresse e-mail de l'utilisateur ;
  • twoManRuleKey : clé stockée par le backend ;
  • twoManRuleSessionId : identifiant de la session créée par le backend pour l'utilisateur auprès de SSKS ;
  • challenge : challenge aléatoire envoyé par e-mail.

Et on change simplement le plugin utilisé pour appeler la fonction sealdSDKInstance.ssks2MR.retrieveIdentity avec ces arguments :

Après avoir appelé cette fonction, l'identité sera récupérée et le SDK sera utilisable.

WARNING

Il est déconseillé de récupérer la même identité avec ssks2MR.retrieveIdentity sur plusieurs appareils en même temps, au même instant exact, par exemple lors de tests automatisés. Veuillez attendre que l'un des appareils ait fini de récupérer l'identité avant de lancer la récupération sur un autre appareil.

# Modification de l'interface utilisateur

Il faut désormais appeler ces fonctions :

  • à la création de compte ;
  • à la connexion.

# Création de compte SignUp.jsx

On commence par rajouter deux hooks d'état :

Puis, on ajoute un composant ChallengeForm qui est un formulaire permettant à l'utilisateur, lorsque mustAuthenticate est true, de taper le challenge et qui appelle handleChallengeSubmit lorsque le formulaire est validé :

Ce ChallengeForm sera utilisé à la place du SignupForm si challengeSession est défini :

Ensuite, on modifie la fin du handleSignupSubmit pour qu'elle affiche à l'utilisateur le ChallengeForm si mustAuthenticate est true, et pour qu'elle enregistre directement l'identité sinon :

Enfin, on crée handleChallengeSubmit qui utilise le challenge complété par l'utilisateur pour sauvegarder son identité sur SSKS :

Ça y est, nous avons mis en œuvre toutes les modifications dans le frontend.

# 2-man-rule par SMS

Une fois le two-man-rule avec un challenge par email implémenté, il est très facile de passer au challenge par SMS. Il va falloir changer le frontend pour demander un numéro de téléphone à l'utilisateur, puis l'enregistrer dans la base de données coté backend. Nous allons ensuite effectuer des modifications mineures à l'utilisation du plugin @seald-io/sdk-plugin-ssks-2mr.

# Modification du modèle User en backend

Dans le fichier backend/models.js, on ajoute à l'initialisation du modèle User la propriété phoneNumber :

# Modification du point d'API et du validateur associé

Il faut ajouter un champ phoneNumber dans le validateur de requête :

Le champ phoneNumber doit ensuite être récupéré et transmis au modèle :

# Modification du point d'API sendChallenge2MR

Les modifications ici sont simples. Il faut modifier le auth_factor. Son type passe de 'EM' à 'SMS', et sa value change pour contenir le numéro de téléphone.

Deuxièmement, il faut modifier le champ template. Celui ne contient plus le code HTML de l'email, mais le texte du SMS. Là encore, le template doit contenir la clé $$CHALLENGE$$ qui sera remplacée par le challenge.

# Modification coté frontend

Les modifications coté frontend sont très similaires à celle du backend. Il va falloir :

  • Ajouter un champ numéro de téléphone dans le SignupForm.
  • Ajouter une clé phoneNumber dans le modèle utilisateur.
  • Modifier le point d'API de création de compte et son appel pour inclure le numéro de téléphone.

Une fois ces trois modifications effectuées, il faut changer les fonctions saveIdentity2MR et retrieveIdentity2MR dans le fichier seald.js. Il faut ajouter un argument phoneNumber à ces fonctions, puis modifier l'argument authFactor lors des appels aux fonctions du plugin. Là encore, le type du facteur d'authentification devient 'SMS', et sa valeur devient le numéro de téléphone.

# Conclusion

Nous avons pu mettre en oeuvre la protection en 2-man-rule complètement, des améliorations peuvent encore être apportées, par exemple au lieu de demander à l'utilisateur de recopier le challenge il est possible de l'inclure dans un magic link vers l'application (attention à ne pas utiliser de variable GET mais un fragment d'URL pour que le serveur ne connaisse pas le challenge).