Un système de vérification de licence logicielle simple et efficace

Introduction

Une application déployée sous la forme d’un Software as a Service dispose d’un grand avantage quand il s’agit de vérifier la légitimité qu’a un utilisateur à l’exploiter: il ne peut se faire qu’en interrogeant un serveur déployé dans un environnement supposément sécurisé. Le système peut donc se contenter d’authentifier l’utilisateur (par exemple avec un logiciel et un mot de passe), puis la vérification des permissions peut se faire dans un contexte fiable.

Il en va de même des applications proposant des fonctionnalités en ligne (comme la plupart des jeux vidéo modernes): le serveur profite du besoin qu’a l’utilisateur de se connecter pour cadenasser l’accès à certaines fonctionnalités.

Il reste cependant des situations où le recours à un service distant placé sous le contrôle d’une entité de confiance n’a pas de sens, n’est pas souhaitable, voire s’avère carrément contre nature. Pensons aux nombreux logiciels embarqués, ou encore aux produits utilisés dans des réseaux d’entreprise non connectés à Internet pour des raisons de sécurité ou de confidentialité.

Cahier des charges

Tout en gardant à l’esprit qu’aucune protection n’est infaillible, comment limiter autant que possible l’utilisation non permise du logiciel?

La licence doit être distincte de l’application. Il ne peut s’agir d’un composant logiciel qu’on vient greffer à l’application, mais plutôt d’une sorte de configuration qu’on peut modifier aisément et que l’application peut prendre en compte à son démarrage ou en cours d’exécution.

Elle ne peut contenir aucune information secrète ou confidentielle. La licence étant accessible à l’utilisateur, elle ne peut contenir aucun élément pouvant compromettre l’application ou son éditeur.

Elle doit être exploitable sur une machine coupée du monde. L’application doit pouvoir la valider sans recourir à un service tiers, qu’il se situe sur Internet ou sur un réseau local.

Elle doit consister en une chaîne de caractères sans symboles spéciaux. L’utilisateur doit pour l’appliquer en la tapant sur son clavier (ou, plus vraisemblablement, en exécutant des commandes dans un terminal), en copiant un fichier ou en l’insérant dans une variable d’environnement. Bien qu’il gère également des fichiers binaires, un système de gestion de déploiement tel que Kubernetes privilégie la manipulation de chaînes de caractères.

Elle doit résister aux manipulations de l’utilisateur. Celui-ci pouvant y accéder, une simple édition ne doit pas permettre l’accès à certaines fonctionnalités normalement non permises par la licence.

Elle doit permettre une spécification riche et extensible. Cela implique la définition de dates de début et de fin de validité de la licence, afin de limiter l’impact de la divulgation d’une licence et de s’assurer que l’utilisateur procède périodiquement à son renouvellement. La spécification doit, de plus, pouvoir comprendre une description des fonctionnalités utilisables ou non par l’intermédiaire de la licence. Le partage de licences entre utilisateurs étant difficilement évitable d’un point de vue technique, on souhaite également pouvoir inclure un identifiant permettant de remonter client à l’origine d’une indiscrétion, ne serait-ce que pour le dissuader d’y recourir.

Mise en œuvre

J’ai eu l’occasion de travailler sur plusieurs systèmes de contrôle de licence qui satisfaisaient divers sous-ensembles des contraintes du cahier des charges. Finalement, je suis parvenu à une solution fort simple, basée sur des techniques éprouvées qui ne réinventent pas la roue et qui satisfont l’ensemble du cahier des charges.

Le cœur de cette solution repose sur une utilisation vaguement originale JWT. Ce token, dont nous avons déjà parlé dans un autre article, est essentiellement un objet JSON signé, éventuellement chiffré, et encodé en Base64.

Lorsqu’il souhaite concéder une licence d’exploitation à un utilisateur, un éditeur crée un JWT dont le contenu comporte l’identifiant de l’utilisateur, une date de début et de fin de validité (avec les claims nbf et exp, respectivement), l’identifiant de l’application (avec le claim aud), ainsi que le détail des fonctionnalités couvertes par la licence (par exemple, sous la forme d’une liste d’identifiants). Le tout est alors signé avec la clef privée de l’éditeur, puis encodé par la clef privée de l’éditeur. Un chiffrement n’est ici pas utilisé.

La charge utile d’un tel token pourrait ressembler à l’exemple ci-dessous:

{
  "aud": "my-app",
  "userId": "John Doe",
  "nbf": 1741799998,
  "exp": 1741803598,
  "features": [ "admin", "reporting", "extra" ]
}

Ce qui, une fois signé et encodé en Base64, donne la chaîne suivante:

NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=

L’éditeur communique à l’utilisateur le JWT ainsi généré, par exemple par e-mail. Celui-ci s’arrange pour le rendre accessible par l’application, par exemple en le stockant dans un fichier ou en le plaçant dans une variable d’environnement.

Mission accomplie?

Cette approche correspond bien à notre cahier des charges. La licence n’est pas intégrée à l’application, mais lui est communiquée au moyen d’une chaîne de caractères alphanumériques que l’utilisateur peut aisément manipuler.

Elle ne contient aucun secret dont un utilisateur trop curieux pourrait profiter. Si son contenu est facilement modifiable, toute altération serait détectée par l’application qui remarquerait que la signature effective du JWT ne correspond plus à celle figurant dans le token.

La clef publique, associée à la clef privée utilisée pour signer le token, n’est par définition par secrète : elle pourrait figurer sur le site Web de l’éditeur, par exemple. Cependant, en embarquant cette clef publique dans l’application, celle-ci devient capable de vérifier les signatures de JWT sans recourir à un service tiers.

Enfin, en choisissant avec soin les données ajoutées au token, l’éditeur peut préciser avec le niveau de granularité adapté les permissions dont bénéficie le détenteur du token.

Limitations

Notre approche n’est pas parfaite.

Longueur de la chaîne

La chaîne de caractères correspondant à un JWT est relativement longue. Si cela reste techniquement possible, elle ne peut raisonnablement être tapée directement au clavier, contrairement aux clefs de licence d’une dizaine de caractères qu’on trouvait très fréquemment avec les logiciels avant l’apparition d’Internet dans nos foyers et nos entreprises. Néanmoins, aujourd’hui, la communication et la copie de chaînes de quelques dizaines ou centaines de caractères d’un ordinateur à un autre ne présente plus de difficulté majeure.

Divulgation du token

Dans la mesure où l’utilisateur intervient dans la procédure de déploiement du token, rien ne l’empêche de communiquer celui-ci à d’autres personnes afin qu’ils en bénéficient. Des dispositions légales s’imposent donc.

On peut cependant limiter ce problème de plusieurs manières. Tout d’abord, la présence d’un identifiant d’utilisateur dans le token inaltérable, car toute modification du token serait détectée par la vérification de sa signature par l’application peut avoir un effectif dissuasif. Cet aspect est renforcé lorsque cet identifiant fait partie d’un rapport que l’application génère, par exemple annuellement, en vue d’une demande de renouvellement du token.

Ensuite, la limitation de la durée de validité d’une licence et la restriction des fonctionnalités qu’elle offre réduit les conséquences d’une divulgation. Elle force de plus les utilisateurs frauduleux à s’échanger périodiquement de nouveaux tokens, ce qui introduit une complexité opérationnelle. Cependant, cette contrainte peut s’avérer nuisible pour les systèmes embarqués, pour lesquels un accès afin de renouveler le token s’avère problématique voire impossible.

Enfin, le token peut mentionner un élément qui identifie de manière unique l’environnement dans lequel l’application peut s’exécuter: celle-ci vérifie alors que c’est bien le cas avant toute exécution. Cette approche me semble cependant désuète dans un système basé sur des machines virtuelles parfois très éphémère.

Altération de la clef publique

Un utilisateur ayant accès au système informatique pourrait tenter de remplacer la clef privée utilisée pour vérifier les signatures de tokens par sa propre clef, ce qui lui permettrait de signer ses propres tokens. En l’absence de connexion avec le monde extérieur, l’application ne peut compter que sur elle-même pour vérifier la validité de cette clef. Une possibilité consiste à vérifier systématiquement l’intégrité de l’application au moment de son démarrage, grâce à des outils cryptographiques. Cela nécessite typiquement le recours à des composants matériels, ce qui rend à nouveau cette possibilité peu réaliste à l’heure des machines virtuelles et des pods.

Conclusion

Dans cette article, j’ai décrit une manière d’exploiter une technique très répandue auprès des applications Web en l’appliquant à un besoin de contrôle de licence.

Pour peu que l’utilisateur ait accès au système, aucune technique n’est complètement sûre, a fortiori lorsque le système ne peut compter sur une connexion à une ressource de confiance. Néanmoins, cette approche est suffisamment robuste pour de nombreux cas d’utilisation. Elle est extensible, compatible avec un fonctionnement hors ligne et simple à mettre en œuvre. Que demande le peuple?

Laisser un commentaire