# 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'experience 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.

La branche sur laquelle est basée cette étape est 4-user-license-token (opens new window) , le résultat final est 5-two-man-rule (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 Secure 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.

# 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, l'instance mutualisée est https://ssks.seald.io/
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 une adresse e-mail valide, ainsi qu'un template d'email 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 tout 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 email. Cet e-mail contiendra un challenge 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 par email 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.

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

# 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).