# Gestion des identités
Après avoir importé et instancié le SDK, il faudra qu'il dispose d'une "identité" pour effectuer d'autres opérations.
Un utilisateur est représenté par plusieurs "identités", la plupart du temps une par appareil. Ces identités sont créées au travers du SDK. Elles peuvent être simplement gardées en mémoire volatile, ou au contraire dans une base de données locale persistante. Elles peuvent également être sauvegardées sur nos serveurs de façon sécurisée pour pouvoir les réutiliser par la suite.
Pour sauvegarder des identités sur nos serveurs, il existe deux modes de protection :
- par mot de passe avec le plugin
@seald-io/sdk-plugin-ssks-password
; - en two-man rule avec le plugin
@seald-io/sdk-plugin-ssks-2mr
.
Pour une gestion manuelle de la protection d'une identité que ne couvriraient pas ces plugins, n'hésitez pas à nous contacter ou utiliser l'export manuel.
# Créer des identités
Un utilisateur de Seald est représenté par plusieurs "identités". Ce sont concrètement des clés privées générées localement sur l'appareil de l'utilisateur avec le SDK.
Elles sont toutes reliées entre elles par une chaîne de signature. Pour plus de détails sur notre technologie, consultez la page dédiée (opens new window).
On distingue deux cas :
- une première identité ;
- les identités suivantes qui y sont rattachées que l'on appelle "sous-identités".
# Première identité
Pour créer une identité pour un nouvel utilisateur, il faut utiliser la fonction sdk.initiateIdentity
.
Cette fonction nécessite un JSON Web Token. Un guide dédié vous expliquera comment générer ces JWT de licence.
# Identifiant personalisé
Il est possible d'ajouter un identifiant personnalisé, appelé userId
, au format string, via l'utilisation d'un JWT.
Ceci peut être fait lors de la création d'une identité, ou plus tard grâce à la fonction sdk.pushJwt
.
Une identité peut avoir plusieurs identifiants. L'ajout d'un identifiant personnalisé se fait via la clé connector_add
définie dans cette documentation
# Sous-identités
À partir d'une identité existante d'un utilisateur on peut créer une
"sous-identité" à l'aide de la fonction sdk.createSubIdentity
avec une instance du SDK déjà initialisée.
Cette sous-identité pourra alors déchiffrer tout ce que peut déchiffrer l'identité initiale : lorsqu'un autre utilisateur chiffrera quelque chose pour cet utilisateur, l'identité et la sous-identité pourront toutes deux le déchiffrer.
On l'utilise typiquement pour avoir une sous-identité distincte par appareil. Par exemple, lors de la connexion de l'utilisateur à un nouvel appareil, on peut récupérer une identité "principale" protégée par mot de passe ou en 2-man rule, puis créer une nouvelle sous-identité liée à celle-ci pour cet appareil. Il est alors possible de stocker cette sous-identité en localStorage (opens new window), et de la récupérer lors de l'ouverture d'une nouvelle page, afin d'avoir des sessions persistantes.
Cette fonction rend un Buffer
(opens new window) similaire
à celui créé par sdk.exportIdentity
et décrit dans le paragraphe Export manuel.
TIP
Pour accélérer la création de sous-identités, vous pouvez pré-générer les clés
privées en appelant la fonction seald.preGenerateIdentityKeys()
à l'avance.
Exemples
Vous pouvez alors importer ce Buffer
(opens new window)
dans une autre instance du SDK avec la fonction sdk.importIdentity
,
ou directement le sauvegarder sur SSKS avec les fonctions sdk.ssks2MR.saveIdentity
ou sdk.ssksPassword.saveIdentity
.
WARNING
Avoir un très grand nombre de sous-identités pour le même utilisateur (plusieurs dizaines) peut ralentir les opérations concernant cet utilisateur, que ce soit la création de nouvelle sous-identité ou même simplement la création d'une session de chiffrement dont il est destinataire.
Dans le cas où un utilisateur aurait un très grand nombre d'appareils, ou se reconnecterait très régulièrement, nous vous conseillons de ne pas créer une sous-identité distincte à chaque connexion, mais simplement utiliser directement l'identité stockée sur SSKS.
# Base de données locale persistante
# Principe de fonctionnement
Par défaut, le SDK Seald n'enregistre sa base de données qu'en mémoire volatile : si vous créez une identité, puis fermez et réouvrez votre onglet de navigateur, l'identité créée ne sera plus accessible et sera perdue, sauf si vous l'avez protégée sur SSKS avec un mot de passe ou en two-man-rule. Dans ce cas, bien qu'ayant une session authentifiée, l'utilisateur doit retaper son mot de passe, ou s'authentifier par email, pour récupérer son identité Seald.
Pour améliorer l'expérience utilisateur, on peut utiliser une base de données
locale persistante, automatiquement stockée par le SDK Seald dans le navigateur
de façon sécurisée, chiffrée par une clée stockée en backend appelée
databaseKey
.
TIP
La base de donnée de Seald utilise nedb
(opens new window),
qui dans le navigateur, n'utilise pas forcément le localStorage, mais choisit
(via localForage
(opens new window)) la meilleure
méthode de stockage entre IndexedDB, WebSQL ou localStorage selon votre
navigateur.
Lorsque l'utilisateur va rouvrir l'application, la databaseKey
sera récupérée depuis le backend via une session authentifiée, et cette clé sera utilisée pour déchiffrer l'encryptedDB
.
# Utilisation d'une base de données persistante
Pour utiliser une base de données persistante, il suffit d'ajouter un argument
databasePath
pour enregistrer la base de données, et un argument databaseKey
pour la chiffrer, à l'initialisation du SDK.
Le serveur peut soit utiliser une unique databaseKey
par utilisateur pour tous
ses appareils, soit au contraire utiliser une databaseKey
différente par
session. Utiliser une databaseKey
différente par session est un peu plus
sécurisé, mais implique un modèle de donnée un peu plus compliqué. Nous
supposerons dans la suite une databaseKey
différente par session.
Ici, databaseKey
et sessionID
doivent être générés et rendus par le serveur.
On rajoute sessionID
dans le databasePath
afin que celui-ci soit unique pour
chaque session.
En effet, si un même utilisateur se déconnecte, puis se reconnecte, le serveur
aura généré une nouvelle databaseKey
. On ne peut donc pas utiliser la même
base de données : il faut que la base de données soit unique pour chaque
session, on fait donc varier le databasePath
en fonction du sessionID
.
TIP
Vous pouvez aussi remplacer databaseKey
par databaseRawKey
.
Attention, databaseRawKey
doit absolument être un string représentant
l'encodage Base64 d'un buffer cryptographiquement aléatoire de 64 octets.
Techniquement, ceci permet d'éviter que databaseKey
soit dérivé avec scrypt
,
ce qui améliore la vitesse de l'instanciation.
Si vous avez le moindre doute, utilisez plutôt databaseKey
.
Exemples
Utilisation de databaseRawKey
:
Génération d'une databaseRawKey
adéquate :
Contrairement aux modes de protection de l'identité sur SSKS, ici le fonctionnement est identique que vous vouliez créer une nouvelle identité et la stocker dans une base de donnée persistante, ou au contraire en récupérer une existante.
Vous trouverez plus de détails, notamment sur ce que vous devez implémenter coté backend, sur la page du projet exemple à propos de la base de données persistante.
# Protection par mot de passe
La protection par mot de passe de l'identité se fonde sur une dérivation d'un mot de passe connu de l'utilisateur en une clé symétrique que l'on utilise pour chiffrer et déchiffrer l'identité.
WARNING
Une fonctionnalité de type "mot de passe oublié" n'est pas implémentable avec cette méthode sans perte de donnée. Si elle est nécessaire dans votre application, utilisez plutôt la protection en 2-man-rule.
# Principe de fonctionnement
Cette identité chiffrée est ensuite stockée sur un service dédié nommé Seald SDK Key Storage ou SSKS.
La protection par mot de passe fonctionne de la façon suivante :
Et la récupération de la façon suivante :
Le plugin @seald-io/sdk-plugin-ssks-password
permet d'effectuer ces opérations
automatiquement.
TIP
Plus précisément, le mot de passe de l'utilisateur est dérivé deux fois :
- une première fois en le combinant avec le
userId
et leappId
pour donner une "clé de stockage" qui n'est pas secrète ; - une deuxième fois en le combinant avec un "sel" aléatoire, le
userId
et leappId
pour donner une "clé de chiffrement" qui elle est secrète.
L'identité est chiffrée avec la "clé de chiffrement". Puis, sont envoyés à SSKS :
- la "clé de stockage" ;
- le "sel" concaténé avec l'identité chiffrée.
L'identité est récupérée en envoyant à SSKS la clé de stockage qui répond le sel concaténé avec l'identité chiffrée. Le sel est alors utilisé pour obtenir la clé de chiffrement qui est à son tour utilisée pour déchiffrer l'identité.
# Sauvegarder l'identité
Pour utiliser cette methode de stockage, il faut ajouter le plugin
@seald-io/sdk-plugin-ssks-password
lors de l'instanciation du SDK, puis, après
la création de l'identité, la stocker sur SSKS à l'aide de
la fonction sdk.ssksPassword.saveIdentity
.
L'identité est désormais sauvegardée.
TIP
Vous pouvez aussi sauvegarder une autre sous-identité, plutôt que l'identité de l'instance actuelle du SDK :
# Récupérer l'identité
Pour récupérer l'identité à la connexion, il faut appeler
la fonction sdk.ssksPassword.retrieveIdentity
,
avec les mêmes arguments userId
et password
que ceux utilisés lors de la
sauvegarde de l'identité
TIP
Si vous utilisez plusieurs fois un mot de passe incorrect, le serveur peut limiter vos requêtes.
Dans ce cas, vous recevrez le message d'erreur suivant :
Request throttled, retry after {N}s
(ce qui signifie Requête bloquée, réessayer après {N}s
),
où {N}
est le nombre de secondes pendant lesquelles vous ne pouvez pas réessayer.
L'instance du SDK est désormais prête à être utilisée !
WARNING
Il est déconseillé de récupérer la même identité avec
ssksPassword.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.
# Changement de mot de passe
Lorsque l'utilisateur change de mot de passe, vous pouvez utiliser
la fonction sdk.ssksPassword.changeIdentityPassword
pour que l'identité soit stockée avec le nouveau mot de passe et supprimer celle
stockée avec l'ancien.
Le mot de passe est désormais changé et seul le nouveau peut être utilisé.
TIP
Si l'ancien mot de passe n'est pas connu (mot de passe oublié typiquement), ce plugin ne permet pas de récupérer l'identité. Si la fonctionnalité de "mot de passe oublié" est nécessaire dans votre application, utilisez plutôt la protection en 2-man-rule.
WARNING
Cette opération ne change pas les clés de l'identité elle-même, elle ne fait que la re-protéger avec un nouveau mot de passe. Si l'ancien mot de passe est considéré comme compromis il faut aussi créer une nouvelle identité et révoquer l'ancienne.
# Mot de passe utilisé aussi pour l'authentification
Dans le cas où vous voudriez utiliser le même mot de passe pour protéger l'identité que pour l'authentification, celui-ci ne peut être transmis au serveur tel quel, sinon le serveur pourrait alors reconstituer la clé de l'utilisateur, brisant ainsi le chiffrement de bout-en-bout.
Il faut alors modifier les étapes de connexion et de création de compte pour introduire à une pré-dérivation du mot de passe à l'aide de fonctions de dérivation sécurisées de sorte que votre serveur n'ait jamais accès au mot de passe brut, mais seulement à une version dérivée.
Vous trouverez un exemple d'implémentation ici.
TIP
Dans le cas où vous ne pourriez pas modifier la fonction d'authentification (par exemple en cas d'authentification fédérée), il faudrait alors utiliser un second mot de passe ou un autre mode de protection de l'identité comme le 2-man rule.
# Personnaliser la dérivation de mot de passe
Comme expliqué précédemment, le mot de passe
fourni par l'utilisateur est dérivé en une "clé de stockage", et une "clé de
chiffrement". Cette dérivation s'effectue avec scrypt
.
Dans le cadre d'une utilisation avancée, vous pouvez néanmoins court-circuiter ce mécanisme, et directement passer une "clé de stockage" et une "clé de chiffrement" brutes en arguments.
La clé de stockage n'a pas besoin d'être secrète par rapport à votre
serveur. Il faut cependant absolument qu'elle soit unique, et secrète de tout
autre utilisateur, car elle permettrait de supprimer l'identité enregistrée.
Elle doit être un string, de 256 caractères au maximum, et composé des
caractères autorisés suivants: /A-Za-z0-9+\/=\-_@./
.
Pour celle-ci, plusieurs stratégies sont possibles :
- vous pouvez utiliser un string aléatoire générée par le serveur, et la passer simplement au front-end
- vous pouvez également le dériver du mot de passe, potentiellement avec un autre sel stocké sur le serveur
La clé de chiffrement ne peut être connue que de l'utilisateur dans son frontend et doit absolument rester secrète de tout autre acteur, y compris votre serveur. Le format attendu est un string représentant 64 octets encodés en Base64. La manière usuelle de faire est de la dériver du mot de passe, si possible avec un sel unique aléatoire généré et stocké par votre serveur.
Exemple de dérivation personnalisée de rawEncryptionKey
avec PBKDF2 :
# Protection en 2-man-rule
Le mode de protection en 2-man-rule consiste à "couper" en deux l'identité à protéger et de placer une moitié sur le back-end de l'application soumis à authentification, et l'autre moitié sur Seald SDK Key Storage (SSKS) soumis à une authentification indépendante.
L'objectif est qu'un utilisateur ait "le droit" de perdre tous ses mots de passe et tous ses appareils sans perte de donnée. La contrepartie est que ce mode n'est pas strictement "de bout-en-bout".
Il est nécessaire d'implémenter côté serveur le stockage d'un secret stocké et remis contre authentification, ainsi qu'une interaction avec SSKS.
# Principe de fonctionnement
Plus concrètement, pour protéger une identité, le protocole est le suivant (dans le cas classique) :
TIP
Dans le cas où une identité a déjà été stockée sur SSKS avec cette adresse email, le serveur SSKS demandera alors une authentification par challenge email, similaire à celle décrite dans la section suivante à propos de la récupération d'une identité, avant de permettre le stockage d'une nouvelle identité.
Pour plus de détails, référez-vous à la page Intégrer le 2-man-rule sur votre backend.
Pour récupérer une identité, on procède de la façon suivante :
Ainsi si le backend est compromis, la encryptedIdentity
stockée sur SSKS
ne le sera pas, et le backend ne peut pas simuler une authentification auprès
de SSKS.
Inversement si SSKS est compromis, la clé twoManRuleKey
ne le sera pas et
SSKS ne peut pas simuler une authentification au backend.
WARNING
Ce mode n'est pas strictement de bout-en-bout dans le sens où la clé permettant
le déchiffrement des données d'un utilisateur peut être récupérée sans le
consentement de l'utilisateur en réunissant twoManRuleKey
sur SSKS et
encryptedIdentity
sur le backend.
TIP
Seald prend les précautions suivantes pour assurer la confidentialité des données stockées sur SSKS :
- utilisation de serveurs non soumis au Cloud Act (opens new window) (chez OVH (opens new window) en France) pour que le mode de protection par 2-man rule soit robuste dans le cadre d'une utilisation d'hébergeurs soumis à l'extra-territorialité du droit américain pour le backend ;
- sur-chiffrement au repos des champs sensibles (
encryptedIdentity
) ; - hachage et salage des adresses e-mail des utilisateurs auxquelles sont
associées les
encryptedIdentity
, de sorte qu'elles ne soient connues que lors de l'exécution. Ainsi, une compromission à froid de SSKS ne permet pas d'associer lesencryptedIdentity
aux adresses e-mail.
WARNING
Les adresses emails et numéros de téléphones envoyés au serveur SSKS doivent absolument être normalisés.
# Protection d'une identité
Pour utiliser cette methode de protection, il faut installer, importer & ajouter
lors de l'instanciation du SDK le plugin @seald-io/sdk-plugin-ssks-2mr
.
Votre serveur applicatif doit ensuite :
- créer un nouvel utilisateur sur SSKS en donnant son adresse email ;
- générer une session SSKS pour celui-ci ;
- générer une clé secrète
twoManRuleKey
pour cet utilisateur (une chaine de caractère aléatoire suffisamment robuste, par exemple un UUID serait un bon choix) - transmettre le
sessionId
et la clé secrètetwoManRuleKey
au front-end que nous symboliserons parAPI.getSSKSSession()
dans l'exemple.
L'utilisateur peut alors stocker son identité à l'aide de la fonction sdk.ssks2MR.saveIdentity
.
Vous trouverez la documentation de l'API de SSKS pour votre serveur applicatif ici.
L'identité est sauvegardée.
TIP
Vous pouvez aussi sauvegarder une autre sous-identité, plutôt que l'identité de l'instance actuelle du SDK :
TIP
Le serveur SSKS ne peut alors pas accéder à l'identité de l'utilisateur, car
il n'a pas accès à la clé secrète twoManRuleKey
gardée par votre serveur. Réciproquement,
votre serveur applicatif ne peut pas y accéder non plus car pour récupérer une identité
il faut un challenge
envoyé par email ou SMS, qu'il ne reçoit jamais et ne peut pas forger.
# Récupération d'une identité
Ensuite, lors d'une nouvelle instanciation du SDK, votre backend doit de la
même manière générer une session pour l'utilisateur, et transmettre le
sessionId
et la clé twoManRuleKey
au front-end. Le serveur SSKS envoie
alors un email contenant un challenge (valable 6h) à l'adresse email de
l'utilisateur.
TIP
Dans un environnement de test, votre serveur peut utiliser l'argument
fake_otp: true
lors de la génération de session, afin de ne pas réellement
envoyer le challenge
. Celui-ci sera alors fixé à 'aaaaaaaa'
.
Pour plus de détail, voir le paragraphe dédié dans le guide à propos du 2-man-rule.
Il faut ensuite appeler la fonction sdk.ssks2MR.retrieveIdentity
avec les arguments userId
, sessionId
, email
, challenge
et twoManRuleKey
.
Votre instance du SDK est alors prête à être utilisée !
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.
# Changement du authFactor
d'un utilisateur
Dans le cycle de vie de l'utilisateur dans votre application, celui-ci peut parfois avoir besoin de changer son adresse email, ou son numéro de téléphone.
Si ce facteur d'authentification est utilisé pour stocker son identité Seald sur SSKS, il va falloir également réenregistrer son identité sur cette nouvelle adresse email ou numéro de téléphone.
Pour ce faire, il n'y a pas de point d'API spécifique dans l'API serveur SSKS, ni de fonction spécifique dans le plugin SSKS du SDK. Il suffit en effet de stocker l'identité sur le nouveau facteur (si l'identité n'est pas présente en local, il faudra préalablement la récupérer sur l'ancien facteur), puis d'utiliser l'API serveur pour supprimer l'identité stockée sur l'ancien facteur.
WARNING
Afin de minimiser le risque de perte de l'identité de l'utilisateur, nous
recommandons de d'abord stocker l'identité sur le nouveau authFactor
avant
de la supprimer de l'ancien.
# Utilisation d'une TwoManRule Key brute
Dans les appels des fonctions ssks2MR.saveIdentity
et
ssks2MR.retrieveIdentity
, vous pouvez aussi remplacer twoManRuleKey
par
rawTwoManRuleKey
.
Attention, rawTwoManRuleKey
doit absolument être un string représentant
l'encodage Base64 d'un buffer cryptographiquement aléatoire de 64 octets.
Techniquement, ceci permet d'éviter que twoManRuleKey
soit dérivé avec
scrypt
, ce qui améliore la vitesse d'exécution.
Si vous avez le moindre doute, utilisez plutôt twoManRuleKey
.
Utilisation de rawTwoManRuleKey
:
Génération d'une rawTwoManRuleKey
adéquate :
# Export manuel
Il est également possible de gérer le stockage d'une identité manuellement.
Pour ceci, vous pouvez utiliser les fonctions sdk.exportIdentity
, et
sdk.importIdentity
.
sdk.exportIdentity
vous
permet d'exporter un Buffer
(opens new window)contenant
l'identité courante de l'instance SDK.
Réciproquement, sdk.importIdentity
importe un Buffer
(opens new window) d'identité dans
l'instance SDK afin de la rendre fonctionnelle.
DANGER
Il est alors à votre charge de vous assurer de la sécurité de ces exports d'identités. Cette sécurité est bien entendue capitale pour assurer la confidentialité des données chiffrées pour cet utilisateur.