Skip to content
← Tous les articles
· Delta1 Labs KeyrightLicences.NETAnalyse approfondie

Une seule clé de licence, trois produits : la revendication de produit, les offres de suite en tant que droits, et la barrière qui laisse une clé Lite déverrouiller Pro

Vous livrez un Designer, un Renderer et une CLI, et votre client veut une seule clé pour les trois, pas trois. Le raccourci évident — donner à chaque application le même slug de produit et laisser la signature faire le reste — est celui qui vend discrètement votre application la plus chère au prix de la moins chère, car une clé qui se vérifie pour la gamme de produits se vérifie dans chaque application de la gamme. Cet article lit ce que la revendication de produit d'une licence Keyright vérifie réellement (c'est une vérification d'audience, pas une vérification de fonctionnalité), expose les trois façons de licencier une suite, montre la faille de la conception à slug partagé avec la barrière exacte qui l'ouvre, et construit la version qui tient : un seul slug pour la gamme, l'appartenance par application sous forme de droits avec espace de noms, et une barrière dans chaque application qui demande « suis-je dans cette offre groupée ? » avant toute autre chose. Il se termine par le chemin de mise à niveau d'une clé mono-application vers une clé de suite, et la solution de repli à deux clients pour les équipes qui ont déjà livré un slug par application.

Vous livrez trois choses : un Designer, un Renderer et une CLI. Ils sont vendus séparément en tant que paliers Lite et ensemble en tant que Suite, et la première chose que demande un client Suite, c’est pourquoi il a trois clés de licence à coller dans trois applications. Question légitime. Une seule clé par client est la bonne réponse, et la manière dont la plupart des équipes y parviennent — donner à chaque application le même slug de produit et laisser la signature se charger du reste — est celle qui vend discrètement votre Renderer au prix de votre Designer.

Cet article traite de la barrière qui fait tenir une clé unique par client : ce que la revendication de produit d’une licence Keyright vérifie vraiment, où le raccourci à slug partagé fuit, et comment l’appartenance par application sous forme de droits la referme.

La revendication de produit est une vérification d’audience

Chaque licence Keyright porte un slug de produit, signé avec le reste de la charge utile. Chaque client Keyright porte le slug qu’il attend. Les deux se rencontrent à la validation :

static readonly KeyrightClient License = KeyrightClient.Initialize(new KeyrightOptions
{
    Product         = "acme-renderer",                     // the audience THIS app accepts
    PublicKeyBase64 = AcmeKeys.LicensePublicKey,           // public key only; safe to ship
    ServiceUrl      = "https://licensing.acme.example",
});

LicenseInfo info = License.Validate();   // fails closed if the key's product != "acme-renderer"

Si un client colle une clé Designer dans le Renderer, la signature est bonne, l’expiration est bonne, et la validation échoue tout de même — info.IsValid vaut false et info.Message indique que la clé a été émise pour un autre produit. C’est exactement le rôle d’une revendication aud sur un JWT : ce jeton m’est-il destiné ? Elle est vérifiée avant la lecture de toute autre revendication.

Ce que la revendication de produit ne fait pas, c’est dire ce pour quoi le détenteur de la licence a payé. C’est de l’identité, pas un droit. Gardez cette distinction à l’esprit, car la faille ci-dessous vient du fait qu’on demande à la revendication de produit un travail pour lequel elle n’a jamais été conçue.

Trois façons de licencier une suite

Il n’existe que trois formes, et deux d’entre elles sont tentantes pour de mauvaises raisons.

Un slug par application, trois clés. acme-designer, acme-renderer, acme-cli, chacun émis séparément. C’est sûr — une clé Designer ne peut pas toucher au Renderer — et c’est ce que vous obtenez par défaut lorsque vous ajoutez la gestion de licence à vos applications une par une. Le coût, c’est le ticket de support par lequel nous avons commencé : trois clés à livrer, trois à coller, trois à renouveler.

Un seul slug pour la gamme, rien d’autre. acme-suite partout ; le client Lite reçoit une clé acme-suite dotée de moins de droits de fonctionnalité, le client Suite reçoit une clé acme-suite qui les porte tous. Une seule clé par client. C’est celle qui fuit, et la section suivante montre exactement comment.

Un seul slug pour la gamme, assorti de droits d’appartenance par application. acme-suite partout, et la clé indique les applications qu’elle couvre. C’est la conception qui tient, et elle coûte un appel IsEnabled par application.

La faille, concrètement

Prenez la deuxième forme. Voici une clé Lite — Designer uniquement — émise sous le slug de la gamme. Elle n’a rien d’anormal :

{
  "product":  "acme-suite",
  "licensee": "acct_8f21",
  "expiry":   "2027-10-16T00:00:00Z",
  "entitlements": {
    "export":   true,
    "max-dpi":  300
  }
}

Et voici la barrière du Renderer, écrite de la manière dont les barrières sont habituellement écrites — valider, puis se brancher sur IsValid :

// Renderer: initialised with Product = "acme-suite"
LicenseInfo info = License.Validate();

if (info.IsValid)             // signature ok, product ok, not expired → true
    RunLicensed();            // ...and the Lite customer is now running the Renderer
else
    RunEvaluation();

Déroulons. Le produit de la clé est acme-suite ; le Renderer attend acme-suite ; la signature se vérifie ; l’expiration est dans un an. Toutes les vérifications passent. IsValid vaut true, et le Renderer s’exécute sous pleine licence avec une clé vendue comme « Designer uniquement ». Aucune falsification, aucune altération — le client a acheté le SKU le moins cher et la revendication de produit, faisant exactement son travail, a indiqué au Renderer que la clé lui était destinée. Elle l’était. La clé n’a simplement jamais dit quelles applications elle couvrait, et personne ne l’a demandé.

Une clé, trois applications : la revendication de produit passe partoutClé Lite (signée)product: acme-suiteapp:designer = trueapp:cli = true(pas de app:renderer)Designer · Product=acme-suiteIsValid ✓ IsEnabled("app:designer") ✓CLI · Product=acme-suiteIsValid ✓ IsEnabled("app:cli") ✓Renderer · Product=acme-suiteIsValid ✓ (revendication OK)sur IsValid seul → DÉVERROUILLEsur app:renderer → refusesous licencesous licencefuitecorrectDeux questionsrevendication :« cette clé est-elle pour ma gamme ? »droitd'appartenance :« CETTE appli est-elle dans l'offre ? »les deux sont signés ;aucun ne peutêtre modifié à la main

La barrière qui tient : l’appartenance en tant que droit

Le remède n’est pas un slug par application — cela sacrifie l’objectif de la clé unique. Il consiste à inscrire l’offre groupée sur la clé, sous forme de droits, et à faire vérifier à chaque application le sien. La clé Suite :

{
  "product":  "acme-suite",
  "licensee": "acct_8f21",
  "expiry":   "2027-10-16T00:00:00Z",
  "entitlements": {
    "app:designer": true,
    "app:renderer": true,
    "app:cli":      true,
    "export":       true,
    "max-dpi":      1200
  }
}

La clé Lite a la même forme avec seulement app:designer défini. Le préfixe app: est une convention, pas une fonctionnalité de Keyright — il maintient l’appartenance visiblement distincte des fonctionnalités comme export et max-dpi, pour que personne ne confonde plus tard « quelles applications » avec « quelles capacités ».

Chaque application pose désormais deux questions, dans l’ordre, et la seconde est la véritable barrière :

// Renderer. Same client, same slug — plus the question that was missing.
LicenseInfo info = License.Validate();

bool licensed = info.IsValid                       // 1. is this key for my product line?
             && License.IsEnabled("app:renderer"); // 2. is THIS app in the bundle?

if (!licensed) { RunEvaluation(info); return; }

// Feature gates come after membership, never instead of it.
if (License.IsEnabled("export"))             EnableExport();
int maxDpi = (int)License.GetLimit("max-dpi", fallback: 150);

Faites passer la clé Lite par ce Renderer. La revendication de produit passe, la signature passe, IsEnabled("app:renderer") vaut false, licensed vaut false. Le Designer, qui demande IsEnabled("app:designer"), se déverrouille. Une seule clé ; chaque application ne voit que la part qui lui a été vendue. Et comme les droits se trouvent à l’intérieur de la charge utile signée, un client ne peut pas plus ajouter "app:renderer": true à une clé Lite qu’il ne peut repousser l’expiration — un seul octet modifié et la signature échoue.

Limitez la vérification par application à exactement une ligne, à exactement un endroit, et exécutez-la avant toute barrière de fonctionnalité. Si une barrière de fonctionnalité s’exécute un jour sans que la barrière d’appartenance ait été franchie, vous avez reconstruit la faille avec des étapes en plus.

Émission : un SKU, un jeu de droits

Du côté de l’émission, un SKU n’est qu’un jeu de droits nommé, si bien que les trois paliers sont trois modèles, pas trois produits :

{
  "product": "acme-suite",
  "skus": {
    "designer-lite": { "app:designer": true, "export": false, "max-dpi": 300  },
    "renderer-lite": { "app:renderer": true, "export": true,  "max-dpi": 600  },
    "suite":         { "app:designer": true, "app:renderer": true, "app:cli": true,
                       "export": true, "max-dpi": 1200 }
  }
}

Faire passer un client de Designer Lite à Suite est une réémission sous le même slug avec le jeu suite. Les applications ne changent pas, rien n’est redéployé, et le client colle une seule nouvelle clé dans l’une quelconque des trois applications — la licence stockée est partagée — et les trois s’allument.

Si vous avez déjà livré un slug par application

Bien des équipes ont ajouté la gestion de licence une application à la fois et ont des clés acme-designer, acme-renderer et acme-cli en circulation. Vous pouvez tout de même parvenir à une clé par client sans bascule générale. Chaque application continue d’accepter son slug hérité et accepte en plus le slug de suite — mais uniquement avec son propre droit d’appartenance :

static readonly KeyrightClient Suite  = KeyrightClient.Initialize(new KeyrightOptions
    { Product = "acme-suite",    PublicKeyBase64 = AcmeKeys.LicensePublicKey, ServiceUrl = Url });
static readonly KeyrightClient Legacy = KeyrightClient.Initialize(new KeyrightOptions
    { Product = "acme-renderer", PublicKeyBase64 = AcmeKeys.LicensePublicKey, ServiceUrl = Url });

static (LicenseInfo info, KeyrightClient via) Gate()
{
    var s = Suite.Validate();
    if (s.IsValid && Suite.IsEnabled("app:renderer")) return (s, Suite);   // suite key, in bundle

    var l = Legacy.Validate();
    if (l.IsValid) return (l, Legacy);                                      // old per-app key

    return (s, Suite);                                                      // fail closed; report the suite diagnosis
}

Les nouveaux clients reçoivent des clés de suite. Les clients existants continuent de fonctionner. Lorsqu’un client hérité effectue une mise à niveau, vous émettez une clé de suite et le chemin hérité cesse simplement d’être celui qui répond en premier. Suivez le nombre de clés héritées encore en circulation à partir de vos registres d’émission, et lorsqu’il atteint zéro, supprimez Legacy. La seule chose à ne pas faire, c’est « simplifier » une application héritée en basculant son unique client vers le slug de suite tout en laissant la barrière sur IsValid — c’est de nouveau la deuxième forme, avec la faille.

Testez la fuite, pas seulement le scénario idéal

Le test qui compte est celui qui aurait détecté la conception à slug partagé. Émettez une clé Lite avec une clé de signature de test et affirmez que la barrière du Renderer la refuse :

[Fact]
public void Renderer_refuses_a_designer_only_key()
{
    string lite = TestIssuer.Issue(product: "acme-suite",
        entitlements: new() { ["app:designer"] = true, ["export"] = true });
    string path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.lic");
    File.WriteAllText(path, lite);

    var renderer = KeyrightClient.Initialize(new KeyrightOptions
        { Product = "acme-suite", PublicKeyBase64 = TestIssuer.PublicKeyBase64, LicenseFilePath = path });

    var info = renderer.Validate();
    Assert.True(info.IsValid);                           // product claim passes — by design
    Assert.False(renderer.IsEnabled("app:renderer"));   // membership does not — the real gate
}

Remarquez ce que documente la première assertion : IsValid est vrai, et c’est correct. Le test existe pour garantir que personne ne livre jamais une version où c’est la seule chose que le Renderer vérifie. Associez-le à son inverse — une clé de suite active les trois — et à un service d’émission factice si vous voulez couvrir aussi l’aller-retour d’activation.

À retenir

La revendication de produit d’une licence est une vérification d’audience — « cette clé est-elle pour ma gamme de produits ? » — et elle est vérifiée en premier, avant toute autre revendication, ce qui en fait précisément le mauvais outil pour décider quelles applications de cette gamme un client a achetées. Donnez un seul slug à toute la gamme pour qu’un client ne détienne qu’une clé, inscrivez l’offre groupée sur la clé sous forme de droits d’appartenance par application, et faites en sorte que chaque application s’appuie sur sa propre appartenance avant l’exécution de toute barrière de fonctionnalité. Cela transforme les suites, les paliers Lite et les mises à niveau en jeux de droits du côté de l’émission, maintient la vérification d’une ligne à l’intérieur de chaque application, et referme le raccourci qui vendrait autrement votre Renderer au prix de votre Designer.

Essayez Nebula.NET

Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.