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:
- La cryptographie dâun Biscuit
- Le Datalog
- 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:
- La clef privée
- La clef publique
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:

- La
next key 0
- La
preuve 0
qui est en fait laclef privée
de lanext key 0
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
- les données à sécuriser
- la clef publique de la paire de clef aléatoire
- la signature générée à partir des données, de la paire de clef racine et de la paire de clef aléatoire
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:
- du Biscuit
- de la clef publique racine
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 :
- les données à falsifier
- remonter à la paire de clef aléatoire au travers de la preuve
- connait la clef racine publique
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.

- Le bloc dâautoritĂ© est signĂ© par la clef racine privĂ©e.
- Le bloc 1 est signé par la clef privée de la preuve 0.
Donc pour vérifier, nous utilisons ces clefs publiques.

- La signature du bloc dâautoritĂ© est vĂ©rifiĂ©e par la clef racine publique
- La signature du bloc 1 est vérifiée par la clef publique de la preuve 0
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:
- user
- group
- user_group

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 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 On vient rĂ©aliser les diffĂ©rentes ramification partant de <img class=ââ width=â80%âsrc=â/biscuit-2/cartesian.pngâ/>user
et group
.Explication du produit cartésien
nombres
et lettres
.nombres
pour aller Ă lettres
.
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 Pour le fait Nous faisons le Et on peut faire de mĂȘme pour le fait user(15)
nâappartenant Ă aucun groupe, nâapparaĂźt dans aucun des faits.En SQL
compta
nous aurionsSELECT u.user
FROM user u INNER JOIN user_group ug
ON u.user = ug.user
WHERE ug."group" == 'compta'
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.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:
- de faits globaux
- de checks
- de rules (faits dynamiques)

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.
- Faits globaux
- Rules
- Checks
- Policy

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 ?

On voit que lâAuthorizer a besoin du fait
user
. Il nâexiste pas de faituser
dans lâAuthorizer.Celui-ci se trouve dans lâAutoritĂ©. Il est donc rĂ©cupĂ©rĂ© pour ĂȘtre utilisĂ© dans le check
check if user($u), $u != 1
.LâAuthorizer a Ă©galement besoin du fait
is_allowed
, celui-ci est dĂ©fini dans lâAutoritĂ©.Or le fait
is_allowed
est dynamique, pour ĂȘtre construit, il demande le faitservice
, qui se trouve dans lâAuthorizer.Le fait
service
est donc injectĂ© dans lâAutoritĂ©, qui peut alors gĂ©nĂ©rer le faitis_allowed
.Le fait
is_allowed
existant dĂ©sormais dans lâAutoritĂ©, il peut alors ĂȘtre utilisĂ© par leallow if is_allowed
Le Biscuit est validé
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:
- des Faits locaux
- des Checks
- des Rules
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 qui authentifie les utilisateurs et émet des Biscuit
- Le Service B qui possĂšde des ressources et valide des Biscuits
- Le Pirate qui tente dâaccĂ©der Ă des ressources dont il nâa pas les droits sur le Service B
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:
- RĂ©vocation de Biscuit : si la clef privĂ©e leak, mais on verra que câest bien plus puissant que ça ^^
- Block 3rd party : pour que lâAuthorizer fasse confiance Ă des faits situĂ©s dans un (ou plusieurs) des blocks du biscuit (et pas que dans lâautoritĂ©)
- les bonnes pratiques
- le fonctionnement de la sérialisation.
Un grand merci Ă ceux et celles qui auront lu cet article et je vous dit Ă la prochaine â€ïž