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

Vérifier une clé de licence n'est pas gratuit : mettre en cache la vérification de signature sans mettre en cache une réponse périmée

Une licence Keyright signée se vérifie par un contrôle de signature RSA, et ce contrôle coûte de vraies microsecondes. Appelez Validate() à chaque requête et vous avez placé de la cryptographie à clé publique sur votre chemin critique sans aucune raison — la signature n'a pas changé depuis le démarrage. Mais vous ne pouvez pas non plus simplement mettre le résultat en cache pour toujours, car la réponse dépend du temps : un droit peut expirer à midi, un bail peut être récupéré, un renouvellement peut élargir le plan. Cet article sépare les deux moitiés d'un contrôle de licence — la vérification de signature coûteuse et cacheable, et l'évaluation bon marché, obligatoirement par appel, de la licence à l'instant présent — mesure ce que chacune coûte, et montre une mémoïsation à deux niveaux qui garde la crypto hors du chemin critique sans jamais renvoyer une réponse en retard d'une minute.

Une licence Keyright est un document signé. Le service d’émission détient une clé privée RSA, signe une charge utile canonique — l’édition, les droits, le verrouillage de nœud, l’expiration — et chaque copie de votre application embarque la clé publique correspondante et vérifie cette signature hors ligne. Cette vérification est le fondement même du schéma : c’est elle qui rend infalsifiable une licence lisible côté client. Elle n’est pas gratuite non plus, et l’erreur de performance la plus courante avec n’importe quel SDK de licence signée consiste à la payer bien plus souvent que les faits ne l’exigent.

Voici l’erreur dans sa forme la plus pure — un contrôle de licence câblé directement dans un gestionnaire d’autorisation ASP.NET Core :

public async Task HandleRequirementAsync(
    AuthorizationHandlerContext context, EntitlementRequirement requirement)
{
    // Called on every single request to a gated endpoint.
    LicenseInfo info = _client.Validate();          // ← RSA verify + parse, every time
    if (info.IsEnabled(requirement.Entitlement))
        context.Succeed(requirement);
}

Validate() est un calcul pur, hors ligne et local — il n’y a pas d’appel réseau ici, le bail a été mis en cache à l’activation. Mais « local » ne veut pas dire « assez bon marché pour être exécuté des milliers de fois par seconde ». Chaque appel relance la vérification de signature RSA sur les octets de la licence, redécode la charge utile base64, la réanalyse et reconstruit l’ensemble des droits, pour ne répondre qu’à une question dont les entrées n’ont pas changé depuis le démarrage du processus.

Ce que l’appel coûte réellement

Il est utile d’être concret sur l’endroit où passe le temps. Un appel à Validate() fait, dans l’ordre : décoder la chaîne de licence en base64, vérifier la signature RSA-2048 sur la charge utile canonique, analyser la charge utile (UTF-8 vers champs), construire le dictionnaire des droits, puis évaluer la licence à l’heure courante. Seule la dernière étape dépend du moment où vous l’appelez. Tout ce qui précède ne dépend que des octets de la licence et de votre clé publique.

décodage base64de la licenceRSA-2048 verifydizaines de µsanalyse charge utile +construction des droitsévaluer à maintenantcomparer expirations · nsconstant pour une licence donnée — à mettre en cachedépend du temps — jamais en cache

Aucun de ces chiffres n’est alarmant en soi — quelques dizaines de microsecondes pour la vérification, quelques allocations éphémères pour l’analyse. Le dégât vient de la répétition. Placez cela sur un chemin par requête à mille requêtes par seconde et vous consommez l’équivalent d’un cœur, en générant un flux constant de déchets mémoire, pour re-prouver une signature déjà valide. Le remède n’est pas une bibliothèque crypto plus rapide. C’est d’arrêter de poser la question coûteuse quand vous connaissez déjà la réponse.

Vérifier une fois, évaluer à chaque fois

L’idée clé est que les deux moitiés ont des durées de vie de cache complètement différentes. La vérification de signature et l’analyse sont immuables tant que le processus détient les mêmes octets de licence — vérifier une seconde fois les mêmes octets contre la même clé publique ne peut pas produire un résultat différent. L’évaluation à l’instant présent est l’inverse : c’est précisément la partie qui est censée changer, parce qu’un droit peut porter sa propre expiration et que l’horloge validée avance.

Séparez-les donc. Vérifiez et analysez une fois, conservez l’instantané LicenseInfo résultant, et à chaque appel ne faites que l’évaluation bon marché, dépendante du temps, contre cet instantané. LicenseInfo est une valeur immuable — le SDK la produit et ne la modifie jamais — elle peut donc être partagée entre threads sans verrou.

public sealed class LicenseSnapshot
{
    private readonly KeyrightClient _client;
    private volatile LicenseInfo _verified;   // signature-verified, parsed once
    private long _verifiedAtTicks;
    private readonly long _refreshTicks = TimeSpan.FromSeconds(2).Ticks;

    public LicenseSnapshot(KeyrightClient client)
    {
        _client = client;
        _verified = client.Validate();                 // pay the RSA cost once
        _verifiedAtTicks = DateTime.UtcNow.Ticks;
    }

    private LicenseInfo Current()
    {
        long now = DateTime.UtcNow.Ticks;
        if (now - Volatile.Read(ref _verifiedAtTicks) > _refreshTicks)
        {
            _verified = _client.Validate();            // re-verify on a short interval
            Volatile.Write(ref _verifiedAtTicks, now);
        }
        return _verified;
    }

    // Hot path: a field read plus a time comparison. No crypto, no allocation.
    public bool IsEnabled(string entitlement) => Current().IsEnabled(entitlement);
}

Deux choses rendent cela correct, et pas seulement rapide. Premièrement, IsEnabled sur l’instantané est la moitié bon marché — pour un droit Keyright qui porte une expiration par droit, IsEnabled compare cette expiration à l’horloge validée au moment de l’appel, de sorte qu’une fonctionnalité qui s’éteint à midi est lue comme désactivée à 12:00:01, même si l’instantané a été vérifié au petit-déjeuner. Le contrôle de signature coûteux est en cache ; la décision sensible au temps ne l’est pas. Deuxièmement, l’intervalle de rafraîchissement est court — des secondes, pas des minutes. Il n’existe que pour capter un bail modifié (un renouvellement qui a élargi le plan, une révocation qui l’a ramené à Free), pas pour rationner le CPU, car le coût CPU a déjà été éliminé en mettant l’instantané en cache entre deux rafraîchissements.

Le piège : mettre en cache la réponse au lieu de la vérification

Le mode de défaillance qui mérite d’être nommé explicitement est celui qui ressemble à une optimisation et qui est en réalité un bug de correction. Il est tentant de mettre en cache le booléen — « export est-il activé ? » — parce que c’est la valeur que veut votre site d’appel :

// WRONG: caches the time-dependent answer.
private readonly ConcurrentDictionary<string, bool> _answers = new();
public bool IsEnabled(string ent) =>
    _answers.GetOrAdd(ent, e => _client.Validate().IsEnabled(e));

C’est rapide et faux. Dès que export expire, ce dictionnaire continue de renvoyer true pendant toute la vie du processus, car rien ne réévalue l’expiration. Vous avez mis en cache la seule chose qui était censée changer, et laissé la seule chose qui ne change jamais — la vérification de signature — s’exécuter à chaque défaut de cache. La bonne séparation met en cache l’instantané (l’invariant) et relance l’évaluation (la variable). Conservez la licence analysée ; ne conservez jamais le verdict.

Niveau lentrevérifier la signaturetoutes les ~2 s / à l'activationLicenseInfoinstantané immuableNiveau rapide — chemin critiqueIsEnabled(e) at now()lecture champ + comparaison expirationnanosecondes, sans allocationécritlit

Quand vous voulez tout de même court-circuiter

Il existe une micro-optimisation légitime par-dessus tout cela, et ce n’est pas la mise en cache du verdict — c’est le fait de sauter le contrôle de rafraîchissement lorsque l’horloge n’a manifestement pas assez avancé pour compter. La méthode Current() ci-dessus fait déjà la garde la moins chère possible : une lecture de DateTime.UtcNow et une comparaison d’entiers. Si le profilage dit que même cela est trop sur un chemin critique extrême, passez l’instantané lui-même à l’appelant et laissez une boucle interne serrée lire les droits directement dessus, en ne rafraîchissant qu’aux frontières d’un lot :

// Batch boundary — refresh once, then evaluate N times with zero overhead.
LicenseInfo info = _snapshot.Current();
foreach (var job in batch)
    if (info.IsEnabled("bulk-process"))
        Process(job);

C’est sûr précisément parce que le lot est court par rapport à l’intervalle de rafraîchissement, et que l’horloge ne peut donc pas avancer de façon significative pendant son traitement. Pour tout ce qui vit plus longtemps — un gestionnaire de requête, un tick de service d’arrière-plan — repassez par Current() afin qu’un droit fraîchement expiré soit vu.

La règle

Un contrôle de licence signée, ce sont deux métiers sous un seul nom de méthode. Vérifier la signature prouve que la licence est authentique, coûte de vraies microsecondes, et a une réponse qui ne change pas tant que le processus détient la licence — vérifiez donc une fois et conservez l’instantané. Évaluer la licence vous dit ce qui est vrai maintenant, coûte des nanosecondes, et est exactement la partie censée changer — ne la mettez donc jamais en cache. Faites la bonne séparation et vous retirez la cryptographie à clé publique de votre chemin critique sans jamais renvoyer une réponse périmée, ce qui est tout l’enjeu : rapide et honnête, et non l’un au détriment de l’autre.

Le SDK Keyright vous fournit la matière première pour cela — un Validate() hors ligne qui renvoie un LicenseInfo immuable, et un bail mis en cache pour que vous ne repassiez jamais par le réseau. Voir droits et feature flags pour le modèle de capacités que porte l’instantané, expiration par droit pour comprendre pourquoi l’évaluation doit rester par appel, et verrouillage fail-closed pour savoir en quoi se résout l’instantané lorsqu’une licence est absente ou révoquée.

Essayez Nebula.NET

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