En route vers Biscuit (Partie 2)

Lecture 34 min. ‱

Table des matiĂšres

Bonjour à toutes et tous! 😀

Le moment tant attendu de la partie 2 de notre série sur le Biscuit est arrivé.

D’abord petit rappel de ce qu’est Biscuit.

Pourboire

Biscuit est un token cryptographique signé numériquement par clef asymétrique et atténuable en droits.

Si la phrase encadrĂ©e vous est incomprĂ©hensible, je vous conseil la lecture de la partie 1 et puis revenir ici. 🙂

Si vous ĂȘtes toujours lĂ  c’est que vous ĂȘtes au fait de ces concepts on va donc pouvoir charger la mule avec d’autres. 😈

Dans cet article nous allons réaliser une courte introduction aux langages logiques et à la notion de Blockchain.

Je vous propose le plan suivant:

  1. La cryptographie d’un Biscuit
  2. Le Datalog
  3. Les usages d’un Biscuit

C’est parti !

Note

Tous les playgrounds sont interactifs 😀

La cryptographie d’un Biscuit

C’est peut-ĂȘtre ce qui est le plus important dans un token cryptograpique, c’est bien sa cryptographie.

Mais c’est quoi la cryptographie ?

Étymologiquement c’est l’art de cacher des choses.

Dans un Biscuit, on ne veut pas forcément cacher des choses mais plus les sécuriser.

Et dans notre cas ce qui va venir servir de verrou, c’est l’alĂ©atoire, ou plus particuliĂšrement la qualitĂ© de l’alĂ©atoire.

Keypair

Cela va permettre de gĂ©nĂ©rer un Ă©lĂ©ment cryptographique que l’on nomme une paire de clef ou Keypair.

L’intĂ©gralitĂ© de la sĂ©curitĂ© est basĂ©e sur l’imprĂ©dictibilitĂ© de la Keypair. Si deux Keypair sont identiques, alors les risques d’usurpations d’identitĂ©s sont trĂšs Ă©levĂ©s.

Une Keypair est composĂ©e d’une paire de clefs:

Mais pas n’importe lesquelles.

Celles-ci ont une relation qui les lie.

Il est trĂšs facile de passer de la clef privĂ©e Ă  la clef publique, autrement dit Ă  partir d’une clef privĂ©e on peut reconstituer la clef publique.

Par contre, il doit ĂȘtre impossible Ă  partir d’une clef publique de retrouver la clef privĂ©e de la paire de clef.

Ainsi, il n’y a aucun danger à laisser traüner une clef publique dans la nature. Chose qui nous sera trùs pratique par la suite.

Nous allons maintenant faire ce que l’on a appris dans la partie 1 signer numĂ©riquement et plus particuliĂšrement par clefs assymĂ©triques.

Sauf que l’on ne va pas utiliser une clef privĂ©e pour signer. Nous allons plutĂŽt utiliser deux paires de clefs diffĂ©rentes.

Une paire de clefs racine. Celle-ci est soit générée aléatoirement, soit récupérée depuis une base de clefs chiffrement.

Et une deuxiÚme paire, elle complÚtement aléatoire.

Créer un Biscuit

A partir de cette soupe de paires de clef et des donnĂ©es que l’on souhaite sĂ©curiser (ou reviendra dessus plus tard 😅), nous gĂ©nĂ©rons une signature.

Mais pas seulement, on Ă©galement la paire de clef alĂ©atoire que l’on dĂ©construit en deux entitĂ©s:

Bon, il est temps de fabriquer notre premier Biscuit ! đŸ€©

Il est composĂ© d’un bloc que l’on nomme une AutoritĂ©.

Dedans, nous allons y retrouver

Puis, et c’est lĂ  que gĂ©nĂ©ralement les gens pĂštent un cĂąble, une clef privĂ©e directement insĂ©rĂ©e dans le token en tant que preuve.

Mais aucun risque, puisque cette clef privĂ©e est dans les faits une clef totalement alĂ©atoire qui ne peut pas servir Ă  usurper d’une quelconque maniĂšre une identitĂ©, car elle ne porte pas ce genre d’information.

Et peut donc, tout comme une clef publique ĂȘtre laissĂ©e dans la nature sans risque de sĂ©curitĂ©.

(En tout cas, tout autant que le token lui-mĂȘme)

Vérifier un Biscuit

Nous avons signé notre Biscuit, il est temps de le vérifier.

Pour cela, nous avons besoin:

Celle-ci provient de la paire de clef racine que nous avons utilisé pour créer notre Biscuit.

Mais ce qui est gĂ©nial, c’est que seul la clef publique nous est nĂ©cessaire.

Autrement dit, nous n’avons pas le souci du Macaron qui nĂ©cessitait que le secret ayant servi Ă  la signature du Macaron soit connu de la personne qui allait vĂ©rifier cette mĂȘme signature.

Pour effectuer la vĂ©rification du Biscuit nous avons besoin d’un dernier ingrĂ©dient.

La clef publique dérivée de la preuve.

La vérification est décomposée en deux parties:

La premiĂšre consiste Ă  vĂ©rifier l’authenticitĂ© des donnĂ©es en comparant la signature vĂ©rifiĂ©e par la clef publique racine.

La seconde vĂ©rifie que le bloc d’AutoritĂ© concerne bien la preuve. Pour cela, nous comparons la clef publique du bloc d’AutoritĂ© Ă  la clef publique de la preuve.

Les deux doivent coĂŻncider, si ce n’est pas le cas c’est que potentiellement le bloc d’AutoritĂ© n’est plus le mĂȘme.

Bon et du coup, comment est-on certain que les donnĂ©es contenues dans le Biscuit n’ont pas Ă©tĂ© falsifiĂ©es ?

Et bien, si un pirate fait évoluer les données, alors il y aura incohérence entre les données et la signature.

Le seul moyen de rendre cohérent la signature serait de la falsifier elle aussi.

Or! Ceci est Ă©galement impossible par le mĂ©canisme mĂȘme de signature du Biscuit.

Le pirate peut connaĂźtre :

Mais, pour peu qu’elle n’ait pas fuitĂ©, il lui est impossible de reconstituer la clef racine privĂ©e.

Et donc, impossible de créer la paire de clefs racine.

Et donc impossible de créer une signature qui serait vérifiée par la clef racine publique.

Et donc impossible de créer un Biscuit en ne connaissant pas la clef privée racine.

Blockchain

De la Blockchain ?? Comment ça, je pensais pas qu’on parlerai de crypto-monnaie ici !!

C’est parce que la Blockchain est bien plus qu’un outil de revendication d’indĂ©pendance monĂ©taire. C’est Ă  avant tout un moyen de stocker de maniĂšre sĂ»re de la donnĂ©es sans faire confiance aux participants.

La conception de la chaĂźne de blocs, va consister Ă  introduire un ou plusieurs nouveaux blocs, au bloc d’AutoritĂ© que l’on possĂšde dĂ©jĂ . Et s’assurer que ces nouvelles donnĂ©es ne puissent pas ĂȘtre fasifiĂ©es et que l’on ne puisse pas non plus falsifier le bloc d’AutoritĂ©.

Et derniÚre contrainte et pas des moindres. Il ne faut pas avoir accÚs à la clef privée racine.

Du coup comment va-t-on s’en sortir ?

C’est Ă  ce moment que l’existence de la preuve devient magique ! đŸ§™â€â™‚ïž

La preuve étant une clef privée, on peut en reconstituer une paire de clef.

Puis en générer une seconde paire aléatoirement.

De cette paire de clef aléatoire on peut en créer une seconde preuve et la clef publique du bloc pour vérifier le prochain.

Ce qui nous permet de signer les nouvelles données sans avoir besoin de clef privée racine.

On se sert à la place de notre paire de clef venant de la preuve précédante.

Et on construit ainsi notre Biscuit atténué.

Nous avons dĂ©sormais un bloc d’AutoritĂ© suivi d’un bloc de donnĂ©es.

Nous avons également remplacé la preuve précédente qui est écrasée à tout jamais.

Vous verrez que ça aussi c’est fait à dessin. 😀

Vérifier le bloc supplémentaire

D’abord, petit rappel de comment est signĂ© notre Biscuit.

Donc pour vérifier, nous utilisons ces clefs publiques.

Or, la clef publique de la preuve 0, est Ă©galement la clef publique du bloc d’AutoritĂ©

Donc la vĂ©ritable vĂ©rification du bloc 1, se fait avec la clef publique du bloc d’AutoritĂ©

Finalement, vérifier notre Biscuit, consiste à

D’abord vĂ©rifier la signature du bloc d’autoritĂ© avec la clef racine publique comme nous l’avons fait prĂ©cĂ©demment.

Puis utiliser, la clef publique du bloc d’autoritĂ© pour vĂ©rifier le bloc 1.

Et finalement vérifier que la clef publique de preuve 1 correspond à la clef publique du bloc 1.

Falsification de bloc

Oui, d’ailleurs, ce dernier test, semble bien inutile. La clef publique du dernier bloc est toujours la clef publique de la preuve.

Oui, mais
 Non


Imaginons que les donnĂ©es qui soit dans le bloc 1, interdisent Ă  une certaine personne d’accĂ©der Ă  une ressource. Il serait bien tentant de supprimer ce bloc gĂȘnant. 😈

Bon, voilĂ  c’est rĂ©glĂ©, par contre la vĂ©rification ne va pas ĂȘtre aussi simple 


Autant, le bloc d’autoritĂ© est trĂšs bien vĂ©rifiĂ©.

Autant, la derniĂšre clef publique de bloc connue est celle de l’autoritĂ©.

Or, celle-ci est différente de la clef publique de la preuve 1 du Biscuit.

Donc, la validation ne passe pas ! ❌

Ok, donc pour retirer un bloc, faut falsifier la preuve. Bon, allons-y!

Ah, oui, mais non 


Tout ce qu’on connaĂźt de la preuve 0, c’est que c’est la clef privĂ©e de la clef publique du bloc d’autoritĂ©.

Or, il est impossible de remonter Ă  la clef privĂ©e en partant d’une clef publique.

Donc encore perdu ! ❌

Et de mĂȘme, il est Ă©galement impossible de falsifier les donnĂ©es du bloc 1, car nous ne pouvons pas remonter Ă  la preuve 0, qui a servi Ă  crĂ©er la signature qui sera vĂ©rifiĂ©e par la clef publique du bloc d’autoritĂ©.

A partir du moment oĂč la preuve 0 est remplacĂ©e par la preuve 1, le coffre fort se referme!

Il est impossible de retirer ou de modifier un bloc sans avoir au préalable la clef racine privée permettant de reconstituer toutes les preuves!

Plus de blocs !

Maintenant que nous avons la logique, nous pouvons commencer Ă  enchaĂźner les blocs.

Le principe reste identique.

On utilise la preuve 1 pour créer la paire de clef de preuve 1.

On génÚre une paire de clef aléatoire qui deviendra la clef publique du bloc 2 et la preuve 2.

On signe nos donnĂ©es avec la mĂȘme formule qui fait intervenir les deux paires de clefs.

Et on remplace la preuve 1, par la preuve 2.

Il est désormais impossible de recréer la preuve 1.

Note

A part la rechercher dans un Biscuit ne comportant que le bloc 1. Mais l’on verra que cela n’a aucune importance, lorsque l’on abordera le contenu des donnĂ©es.

Bon et du coup, mĂȘme principe pour N blocs.

La seule différence est la preuve qui est la preuve ${n-1}$

Et pour vérifier cela devient mécanique.

On part du bloc d’autoritĂ© que l’on vĂ©rifie avec la clef publique racine, puis on utilise la clef publique du bloc d’autoritĂ© pour vĂ©rifier le bloc 1, puis on utilise la clef publique du bloc 1, pour vĂ©rifier le bloc 2, etc 


Pour finalement vérifier le bloc $n$, en utilisant la clef publique du bloc $n-1$.

Et bien Ă©videmment, on s’assure de la conformitĂ© de la chaĂźne en vĂ©rifiant que la preuve du Biscuit correspond bien Ă  la clef publique du dernier bloc.

Le Datalog de Biscuit

Bon c’est magnifique, nous avons un coffre-fort, il est temps d’y mettre des bijoux et des documents prĂ©cieux ! đŸ€‘

Mais au lieu d’y mettre un simple clef/valeur comme nous avons pu le faire avec le JWT ou le Macaron. Nous allons introduire une sĂ©mantique.

Pourquoi faire ceci ?

Et bien, le problĂšme du Macaron outre l’obligation de vĂ©rification au moyen d’un secret partagĂ©, est l’absence de formalisme dĂ©fini lors de la vĂ©rifications des caveats.

Autrement dit, chacun peut implémenter comme il le désire ses rÚgles, en fonctions des données qui lui sont fournies.

Il existe des bibliothÚques toute faites pour générer ses Verifiers mais le Macaron reste un clef-valeur sans aucune intelligence.

Biscuit, prend le pari d’utiliser un langage de programmation pour dĂ©finir le contenu cette intelligence.

Ce langage se nomme le Datalog.

Oui, l’article Wikipedia fait mal Ă  la tĂȘte ^^

Essayons de comprendre pas Ă  pas le fonctionnement de celui-ci. Et pourquoi c’est cool une fois qu’on a saisi son intĂ©rĂȘt! 😁

Faits

Le premier concept Ă  assimiler est le Fait.

Un fait est de l’information.

Dire qu’un utilisateur possùde l’identifiant #1 est un fait.

Dire qu’un utilisateur est dans le groupe admin est Ă©galement un fait.

Dire que le Biscuit a été créer le mercredi 1 mars 2023 à 10:10 est aussi un fait.

En Datalog un fait est représenté par un identifiant, suivi entre parenthÚses de la valeur de ce fait.

Par exemple, ici, je représente un fait user de valeur 1.

user(1)

Il est possible de mettre autant de fait que l’on dĂ©sire

Ici, je représente les faits énoncé plus haut.

user(1); group("admin"); time(2023-03-01T10:10:00+01:00)

Un mĂȘme fait peut apparaĂźtre plusieurs fois.

Par exemple pour reprĂ©senter qu’un utilisateur est dans deux groupes Ă  la fois.

user(1); group("admin"); group("publisher");

Un fait peut également contenir plusieurs valeurs.

user_group(1, "admin");

Donc de maniĂšre gĂ©nĂ©rale un fait peut-ĂȘtre dĂ©fini par un identifiant suivi de 1 Ă  plusieurs valeurs.

 

Attention

Un fait doit posséder au moins une valeur.

Le fait bad sans valeur est incorrect.

bad()

On peut alors s’amuser Ă  construire des tables de donnĂ©es.

Ce terme de table n’est pas anodin.

Il faut rĂ©ellement visualiser les fait comme des lignes dans une table d’une base de donnĂ©es SQL.

Prenons ce jeu de faits.

//membres des groupes user_group(12, "admin"); user_group(12, "it"); user_group(13, "it"); user_group(14, "compta"); // utilisateurs user(12); user(13); user(14); user(15); // groupes group("admin"); group("it"); group("guest"); group("compta");

Nous alors sommes face Ă  3 tables:

gardez bien en tĂȘte cette reprĂ©sentation sous forme de tables, elle nous sera bien utile. 😁

Fait dynamique

Un autre concept de Datalog est la capacitĂ© Ă  crĂ©er des fait au travers d’autres faits.

Je vais nommer ça des Faits dynamiques

Un fait dynamique se construit en venant piocher des informations venant d’autres faits.

Exemple, nous voulons recréer les faits user_group à partir des faits user et group.

//--- Déclarations des faits // utilisateurs user(12); user(13); user(14); // groupes group("admin"); group("it"); group("compta"); // --- Déclaration dynamique des membres des groupes user_group($user, $group) #<= user($user), group($group);

Alors que s’est-il passĂ© ?

En haut la déclaration du Datalog.

Les tables et la définitions de la maniÚre de créer le fait user_group et donc la table correspondante.

En bas, l’ensemble des entrĂ©es par tables.

Tout se passe comme si vous effectuiez une opération de INNER JOIN entre deux tables.

SELECT user.user, user.group FROM user INNER JOIN group;

Ce qui créé la table virtuelle user_group.

Qui vient s’additionner au prĂ©cĂ©dentes qui Ă©tait user et group.

Explication du produit cartésien

Pour rappel, un INNER JOIN sans clause ON est l’application d’un produit cartĂ©sien entre plusieurs ensembles d’entitĂ©s.

Et qu’est ce qu’un produit cartĂ©sien ? Il s’agit de l’ensemble des combinaisons possibles entre les Ă©lĂ©ments des diffĂ©rents paquets d’entitĂ©s.

Exemple les paquets nombres et lettres.

On vient réaliser les différentes ramification partant de nombres pour aller à lettres.

<img class=“” width=“80%“src=”/biscuit-2/cartesian.png“/>

Fait dynamique contraint

Tout cela c’est parfait, mais on manque de contrîle sur ce qui se passe.

Peut-on empĂȘcher la crĂ©ation de faits si par exemple, certaines contraintes ne sont pas respectĂ©es ?

La réponse est oui.

Pour cela, penchons-nous un peu plus sur la maniĂšre dont on gĂ©nĂšre un fait Ă  partir d’un autre. D’ailleurs ce processus s’appelle une RĂšgle.

Ici, nous avons un fait x avec une valeur de true.

La rĂšgle que l’on dĂ©fini est x est vrai.

Lorsque cela est véridique alors le fait x est vrai existe.

x(true); x_est_vrai($X) #<= x($X), $X;

Par contre, si ce n’est pas la rĂ©alitĂ© alors le fait x est vrai n’existera pas.

Par exemple si le fait x n’existe pas.

Ou que la valeur ne correspond pas.

Dans le premier cas, l’évaluation se stoppe car le fait x ne peut pas ĂȘtre trouvĂ©, dans le deuxiĂšme cas, le fait x existe, mais sa valeur est false.

C’est lĂ  que l’on dĂ©couvre une autre facette de l’évaluation.

Il est possible soit de faire du pattern matching sur des faits, soit d’utiliser les valeurs des faits qui ont Ă©tĂ© matchĂ©es.

Pour chaque analyse, chacune séparées par une virgule ,. Soit ça match, soit ça renvoie true.

y(true); x(false); x_est_vrai($X) #<= x($X), $X;

Dans les deux cas, on remarque que le fait x est vrai n’est pas créé.

Une fois que l’on a compris la logique, on peut alors l’utiliser pour venir vĂ©rifier des comportements plus complexes, comme filtrer de la donnĂ©e.

En cas positif

Ou négatif

// déclaration des faits x(12); x(9); below_10($X) #<= x($X), $X < 10;

On voit alors que seul below_10(9) a été généré, le x(12) a été filtré.

Avec ce que l’on a appris, nous pouvons faire des choses intĂ©ressantes, comme par exemple grouper les utilisateurs.

//--- Déclarations des faits // utilisateurs user(12); user(13); user(14); user(15); // groupes user_group("admin", 12); user_group("it", 12); user_group("it", 13); user_group("compta", 14); // --- Déclaration dynamique des membres des groupes compta($user) #<= user($user), user_group($group, $user), $group == "compta"; it($user) #<= user($user), user_group($group, $user), $group == "it"; admin($user) #<= user($user), user_group($group, $user), $group == "admin";

Nous avons bien deux utilisateurs it, et un compta et un admin.

Le user(15) n’appartenant à aucun groupe, n’apparaüt dans aucun des faits.

En SQL

Pour le fait compta nous aurions

SELECT u.user
    FROM user u INNER JOIN user_group ug
    ON u.user = ug.user
    WHERE ug."group" == 'compta'

Nous faisons le JOIN en nous rattachant sur l’indenticitĂ© via le ON u.user = ug.user des valeurs de colonnes entre les tables, puis nous effectuons une vĂ©rification sur les valeurs qui correspondent.

Et on peut faire de mĂȘme pour le fait it.

SELECT u.user
    FROM user u CROSS JOIN user_group ug
    ON u.user = ug.user
    WHERE ug."group" == 'it'

Note

Syntaxe alternative

Il existe une syntaxe alternative qui se base sur le fonctionnement mĂȘme de Datalog.

//--- Déclarations des faits // utilisateurs user(12); user(13); user(14); user(15); // groupes user_group("admin", 12); user_group("it", 12); user_group("it", 13); user_group("compta", 14); // --- Déclaration dynamique des membres des groupes compta($user) #<= user($user), user_group("compta", $user); it($user) #<= user($user), user_group("it", $user); admin($user) #<= user($user), user_group("admin", $user);

Celle-ci repose sur la capacité de Datalog de venir matcher des faits selon des informations précises.

Pour le fait it.

On match tous les faits user_group dont la premiĂšre valeur est “it”, puis l’on vĂ©rifie que la valeur du fait user($user) correspond au user_group("it", $user).

Si ces deux conditions sont remplies, alors le fait it($user) peut exister et prend la valeur du $user correspondant.

On parle d’unification.

Je vais ĂȘtre tout Ă  fait honnĂȘte avec vous, c’est encore une notion trĂšs vague dans mon esprit, mais ça ne nous empĂȘche pas de l’utiliser 😁

Attention

Le Datalog peut réserver quelques surprises !

On peut se dire, du coup si je veux toutes les personnes non-it il suffit de faire :

//--- Déclarations des faits it(12); it(13); // utilisateurs user(12); user(13); user(14); // --- Déclaration du fait dynamique non_it($u) #<= user($u), it($i), $u != $i;

Mais comme vous pouvez le voir ça ne marche pas.

Pourquoi, et bien rappelez-vous, avant d’évaluer les valeurs, on construit d’abord l’ensemble des possibilitĂ©s.

Ce qui fait, que certaines combinaisons, gĂ©nĂšre des faits, alors que l’on ne le voudrait pas.

Pour cela, il faut ruser, et réfléchir différemment.

Au lieu de dĂ©clarer plusieurs faits user_group si celui-ci appartient Ă  plus d’un groupe. Nous allons plutĂŽt dĂ©finir un tableau de droits par utilisateur.

//--- Déclarations des faits user_groups(12, ["admin", "it"]); user_groups(13, ["it"]); user_groups(14, ["compta"]); user_groups(15, []); user_groups(18, ["it"]); // --- Déclaration du fait dynamique non_it($user) #<= user_groups($user, $groups), !$groups.contains("it"); guest($user) #<= user_groups($user, []);

On peut alors vĂ©rifier facilement la non-existence de la valeur “it” dans le tableau $groups et l’opĂ©rateur de nĂ©gation !.

De mĂȘme, avec cette nouvelle typologie, nous pouvons Ă©galement vĂ©rifier si un utilisateur n’appartient Ă  aucun groupe. Pour cela on match sur le tableau vide [].

Bref, Datalog est puissant mais demande de rĂ©flĂ©chir d’une maniĂšre diffĂ©rente.

Mais c’est ça qu’est bon ! đŸ€©

Vérifier des faits

Pour le moment, on Ă©crit des faits et on en gĂ©nĂšre Ă  partir d’autres mais on ne fait pas grand chose cĂŽtĂ© validation. C’est quand mĂȘme la promesse que je vous ai fait par rapport au Macaron.

Remédions à cette situation, en introduisant encore un nouveau concept.

Il s’agit des Checks.

Un Check prend un fait et vĂ©rifie sa cohĂ©rence. Par cohĂ©rence, j’entends est-ce qu’il existe ou non ?

Bien entendu, un Check vérifiera également un Fait dynamique.

Ou juste, un enchaünement de faits, dynamique ou non d’ailleurs.

Prenons un exemple extrĂȘmement simple. Nous voulons vĂ©rifier que le fait x existe, quel que soit sa valeur.

x(5); check if x($x);

Ok, easy!

Maintenant plus compliqué.

On cherche à savoir si la valeur du fait x est inférieur à 3.

x(5); check if x($X), $X < 3;

Et oui, c’est la mĂȘme syntaxe que lors de la gĂ©nĂ©ration des faits dynamiques contraints.

Ici, la valeur est de 5 ce qui excĂšde 3. Cela signifie que le Check ne passe pas.

Et lorsqu’un Check ne passe pas, le Biscuit est automatiquement rejetĂ©.

Voyons comment ce se dĂ©roule lorsqu’il y a plusieurs checks dans une validation.

fait_1(5); check if fait_1($x), $x < 10; check if fait_1($x), $x > 3;

Tout se passe bien.

fait_1(2); check if fait_1($x), $x < 10; check if fait_1($x), $x > 3;

Le deuxiÚme échoue.

fait_1(3); check if fait_1($x), $x > 3; check if fait_1($x), $x < 10;

Le troisiÚme également.

Par contre, il y a un message: No policy matched.

Police: papiers s’il vous plaüt

Nous allons encore rajoutĂ© un dernier concept, il s’agit des Policies qui veut dire “stratĂ©gies” en bon français. C’est Ă  dire comment doit-on Ă©valuer un Biscuit.

Tout comme les checks, nous pouvons évaluer tout un tas de choses.

Il existe deux variantes possibles:

Les Allows qui sont vrais si le contenu Ă©valuer l’est.

Et les Denies qui marchent à l’inverse.

Mais sa maniÚre de fonctionner est différente.

Si un seul check Ă©tait en Ă©chec, l’ensemble de l’évaluation Ă©tait en Ă©chec.

Pour une police c’est diffĂ©rent.

fait_1(5); allow if fait_1($x), $x < 10; allow if fait_1($x), $x > 3; deny if true;

Si une police ne correspond pas, on passe Ă  la suivante.

fait_1(5); allow if fait_1($x), $x > 10; allow if fait_1($x), $x > 3; deny if true;

Et ainsi de suite.

fait_1(5); allow if fait_1($x), $x > 10; allow if fait_1($x), $x < 4; deny if true;

Jusqu’à atteindre ici le deny qui est toujours vrai, mais seulement Ă©valuĂ© quand c’est le dernier choix possible.

Il est également possible de mélanger les allow et le deny pour modifier les comportements

fait_1(5); allow if fait_1($x), $x > 10; deny if fait_1($x), $x < 6; deny if true;

Et finalement, pouvoir tout mélanger: des checks et des policy.

fait_1(8); check if fait_1($x), $x < 10; check if fait_1($x), $x > 3; allow if fait_1($x), $x > 7; deny if true;

En rĂ©sumĂ©, tous les Checks doivent ĂȘtre valides, mais seulement une seul Policy valide est nĂ©cessaire.

Attention

Attention, tout de fois, Ă  l’ordonnencement des policy, en effet le premier qui match, sera celui qui dĂ©finira l’état de validation.

fait_1(5); check if fait_1($x), $x < 10; check if fait_1($x), $x > 3; allow if true; allow if fait_1($x), $x > 7; deny if true;
fait_1(5); check if fait_1($x), $x < 10; check if fait_1($x), $x > 3; allow if fait_1($x), $x > 7; deny if true;

Le allow if true court-circuite les autres évaluations.

Construire un Biscuit

Nous avons toutes les piùces du puzzle. Plus qu’à mettre de l’ordre dans tout ça et organiser ce joyeux systùme de concepts.

Tout d’abord petite remise au point sur ce qu’est un Biscuit.

C’est une structure de donnĂ©es qui est formĂ©e d’une AutoritĂ© et optionnellement de blocs.

Maintenant regardons plus en détails ces différents composants.

Autorité

Nous allons commencer simple. Nous allons faire un Biscuit, qui pourrait s’apparenter à un token JWT avec du Datalog dedans.

Pour ce faire nous allons remplir l’AutoritĂ©.

Elle peut ĂȘtre composĂ©es:

Prenons par exemple le Biscuit suivant, un Biscuit qui correspond Ă  l’utilisateur #12 et qui est limitĂ© dans son utilisation aux services “compta” et “banque”.

Ce Biscuit n’est pas valide car le fait service , n’existe pas.

Par contre, nous sommes en mesure de dĂ©terminer l’authenticitĂ© des donnĂ©es, car seul un possesseur de la clef privĂ©e racine peut avoir signĂ© ce Biscuit.

user(12); is_allowed($s) #<= service($s), ["compta", "banque"].contains($s); check if is_allowed($s);

Pour le moment, ce Biscuit ne fait rien par lui-mĂȘme, il dĂ©finit des faits et des checks.

Mais rien ne met en mouvement ce Datalog.

Ce mouvement va provenir d’un dernier acteur.

Authorizer

L’Authorizer. Son rĂŽle est de valider ce qui doit l’ĂȘtre.

Il prend en entré un Biscuit, et le valide.

Pour rĂ©aliser la validation, l’Authorizer se base Ă©galement sur du Datalog.

Mais un Datalog qui possùde plus d’outils.

On peut alors implémenter cet Authorizer et le Biscuit à vérifier.

Ici nous nous retrouvons avec un Biscuit qui contient le mĂȘme identifiant utilisateur #12 et un fait dynamique is_allowed vrai si le service est soit la “compta” soit la “banque”.

La diffĂ©rence est que le check a Ă©tĂ© dĂ©portĂ© dans l’Authorizer sous la forme d’une Policy allow.

Cet Authorizer définit également le fait service, ici à banque.

user(12); is_allowed($s) #<= service($s), ["compta", "banque"].contains($s); service("banque"); check if user($u), $u != 1; allow if is_allowed($x); deny if true;

Comment se passe la validation ?

Important

Nous pouvons faire ces opĂ©rations car nous avons confiances dans les donnĂ©es de l’AutoritĂ© du Biscuit ❀

Chose irrĂ©alisable sur des caveats de Macaron, par exemple, car on ne peut ĂȘtre certain de leur authenticitĂ© !

Atténuation

Nouveau cas d’usage.

Au lieu de dĂ©finir, le check de service dans l’Authorizer, nous souhaitons faire les choses diffĂ©remment.

Imaginez, vous possédez un Biscuit qui possÚde un fait comportant votre ID utilisateur.

Celui-ci, vous donne accÚs de maniÚre illimité à tout vos services.

L’Authorizer du service possùde un fait service, avec le nom du service en question et c’est tout.

L’authorisation est inexitante, on ne rĂ©alise qu’une authentification. Nous verrons l’autorisation de ressource dans l’exemple suivant. 😀

On croit le Biscuit, comme Ă©tant l’utilisateur 12, on lui ouvre une session, et c’est bon. 🙂

Bien sĂ»r, vous ne pouvez pas accĂ©der au session d’un autre utilisateur. 😎

user(12) service("banque"); allow if true;

Maintenant, pour une raison qui vous est propre. Vous souhaitez partager votre session avec un ami.

Vous lui donnez le Biscuit, et il se retrouve Ă  pouvoir accĂ©der Ă  tous les services de votre utilisateur !! đŸ˜±

Vous ne pouvez pas modifier l’AutoritĂ© de votre Biscuit, car vous n’avez pas la clef privĂ©e racine en votre possession.

Par contre, Biscuit est atténuable par bloc.

Un bloc peut posséder:

On revient juste aprùs sur la notion de “faits locaux”.

On peut alors rajouter un bloc qui va venir limiter les services accessibles.

C’est ce Biscuit attĂ©nuĂ© en droits que vous allez transmettre Ă  votre ami.

Ce qui nous donne cĂŽtĂ© Authorizer la mĂȘme chose, mais pourtant nous avons pu dĂ©grader nos droits et les dĂ©lĂ©guer Ă  une autre personne.

Cet ami, pourra alors lui-mĂȘme attĂ©nuer les droits qu’il possĂšde en rajoutant un nouveau bloc, et ainsi de suite ! 😍

user(12); check if service($s), ["compta", "banque"].contains($s); service("banque"); allow if true;

Si notre ami tente d’accĂ©der Ă  un service dont on ne veut pas, voici ce qui se passe.

user(12); check if service($s), ["compta", "banque"].contains($s); service("it"); allow if true;

Les portes restent closes. 😈

SĂ©curitĂ© sur l’attĂ©nuation

Alors, pourquoi parler de faits locaux dans le cadres des blocs qui ne sont pas l’AutoritĂ© ?

Prenons ce Biscuit

Il possĂšde un certain ID.

Si on croie tous les faits qu’importe leur provenance.

Qu’est ce qui empĂȘche de rajouter un bloc avec l’ID que l’on dĂ©sire ?

Heureusement, les concepteurs de Biscuit ne sont pas fous et ne permettent pas de faire ce genre de choses.

Seuls les faits de l’AutoritĂ© sont crus.

user(12); user(1) allow if user(1); deny if true;

Exemple complet

Allez, dernier effort!

Un exemple complet pour rĂ©capituler tout et donner plus de concret Ă  ce que j’ai dĂ©roulĂ© depuis le dĂ©but de cet article. 😁

Comme dans les piÚces de théùtres, présentons les acteurs.

Nous avons:

Le Service A signe ses Biscuit avec une clef privée A.

Le Service A, n’a qu’une confiance limitĂ©e du Service B, mais peut sans crainte lui transmettre la clef publique A.

Le Service B, ne croit pas le Pirate qui lui n’a pas connaissance de la clef privĂ©e A.

Vous en tant qu’utilisateur abonnĂ© au Service B, vous tentez d’accĂ©der Ă  une ressource qui vous appartient.

Vous rĂ©alisez une requĂȘte en lecture sur le film matrix.

Mais vous vous faites jeter, il vous manque le Biscuit approprié.

Vous vous authentifiez donc sur le Service A, qui a condition d’un mot de passe correct, vous dĂ©livre un Biscuit.

Celui-ci possÚde un fait user représentant votre identifiant utilisateur.

Vous refaites la mĂȘme requĂȘte mais avec le Biscuit.

La premiĂšre chose que le Service B va rĂ©aliser c’est de vĂ©rifier l’authenticitĂ© du Biscuit en utilisant la clef publique A.

S’il a bien Ă©tĂ© signĂ© par la clef privĂ©e A, alors la vĂ©rification passe.

Une fois que cette vĂ©rification est passĂ©e, rien n’empĂȘche d’utiliser le Biscuit comme un token JWT et venir rĂ©cupĂ©rer le fait user et surtout sa valeur qui est l’identifiant de l’utilisateur.

Nous pouvons faire cela, car nous avons la certitude que seul le Service A a pu dĂ©finir cet ID. (Ă  condition que la clef privĂ©e n’est pas leak).

Pourquoi faisons-nous ça ?

Et bien, nous pouvons avoir des millions d’utilisateurs abonnĂ©s. Or nous stockons en base de donnĂ©es les droits associĂ©s Ă  ces ressources.

Et nous allons ĂȘtre encore plus malin, en ciblant prĂ©cisĂ©ment la ressource que l’utilisateur souhaite joindre.

Or, la requĂȘte d’API, nous fourni cette information.

Nous pouvons ainsi crĂ©er une requĂȘte SQL la plus optimisĂ©e, pour ne renvoyer que les rĂ©sultats voulus.

En combinant l’ID rĂ©cupĂ©rĂ© du Biscuit et la ressource venant de la requĂȘte.

On peut alors transformer les rĂ©sultats de la requĂȘte SQL en faits rights.

Et nous avons un autre fait operation que l’on obtient, lĂ  aussi de la requĂȘte API.

Finalement, nous pouvons gĂ©nĂ©rer l’Authorizer suivant.

Il vĂ©rifie que l’opĂ©ration demandĂ© par l’utilisateur est bien possible sur la ressource.

Nous validons ensuite le Biscuit avec cet Authorizer.

Finalement, le Service B rĂ©pond Ă  l’utilisateur avec son film.

Si l’utilisateur, tente d’accĂ©der Ă  une resource dont il n’a pas les droits ou qui n’existe pas.

Il sera rejeté.

Si un pirate, tente d’accĂ©der Ă  vos ressources en se faisant passer pour vous, il pourra crĂ©er un Biscuit, avec l’AutoritĂ© qu’il faut oui.

Mais pas avec la bonne signature.

La validation d’authenticitĂ© du Biscuit Ă©choue et on renvoie une erreur au pirate.

Maintenant, vous avez un ami, vous voulez lui montrer votre super collections de films mais pas autre chose.

Vous lui passez un Biscuit, mais pas n’importe lequel.

Un Biscuit Films.

Celui-ci est attĂ©nuĂ© en droits par l’ajout d’un bloc Check qui vĂ©rifie que la resource commence par “/films/”.

Ce nouveau Biscuit est alors celui-ci. Et vous avez pu faire cette attĂ©nuation par vous mĂȘme, aucun besoin de le demander au Service A de rĂ©emmettre un Biscuit diffĂ©rent.

Vous ĂȘtes autonome !! 😎

Votre ami fait alors une requĂȘte sur le Service B avec son Biscuit tout chaud.

On valide alors le Biscuit Films avec la clef publique A, comme le Biscuit originel a été édité par la clef privée A, la validation passe.

On unifie les mondes du Biscuit et ce de l’Authorizer.

Le Datalog se déroule.

Et on rĂ©pond Ă  l’ami de l’utilisateur avec le film.

S’il tente d’accĂ©der Ă  une ressource dont il n’a pas les droits, il sera jetĂ©.

Vos comptes en banque sont en sécurité ^^

Je vous propose maintenant en guise de “devoir maison” et de rĂ©compense pour ĂȘtre arrivĂ© jusqu’ici.

Un petit exercice.

Voici un Playgound, modĂ©lisez l’exemple qu’on vient d’étudier.

A vous de bosser ^^

Je donnerai la réponse sur twitter et plus tard dans cet article.

Conclusion

Bon maintenant que l’on a Ă©ffleurĂ© la surface de Biscuit, nous allons 


Je dĂ©conne ! 😁

Cet article est déjà bien trop long !

Dans la prochaine partie, la 3 donc.

Nous verrons un certain nombres de concepts manquants:

Un grand merci Ă  ceux et celles qui auront lu cet article et je vous dit Ă  la prochaine ❀