Anatomie d'un fichier de licence IronLock .lic

CryptographieLicensing
← Tous les articles

Structure générale du .lic

Un fichier de licence IronLock v2 est un document JSON signé ECDSA P-384. La signature est intégrée dans le JSON lui-même, ce qui simplifie la distribution : un seul fichier contient à la fois les données de licence et leur preuve d'authenticité.

# Structure complète d'un .lic IronLock v2
{
  // ── Champs obligatoires ──
  "format_version": "2.0",
  "licence_id":     "LIC-2026-A3F8C2E1",
  "product":        "MonApp",
  "product_version":"1.1.0",
  "hardware_id":    "a3f8c2e1d4b7...",
  "issued_at":      "2026-01-15T10:00:00Z",
  "expires_at":     "2027-01-15T10:00:00Z",

  // ── Champs optionnels ──
  "licensee":       "Entreprise SAS",
  "max_activations":1,
  "grace_days":     7,
  "features":       ["module_analytics", "export_pdf"],
  "custom":         {"client_ref": "CLI-0042"},

  // ── Signature ECDSA P-384 ──
  "signature":      "base64_ecdsa_p384_signature..."
}

Champs obligatoires

Champs optionnels

Extensions custom

Le champ custom accepte n'importe quel objet JSON. Il est inclus dans le payload signé, donc toute modification invalide la signature :

"custom": {
  "client_ref":    "CLI-0042",
  "contract_type": "enterprise",
  "seats":        10,
  "reseller":     "Partenaire SARL",
  "notes":        "Renouvellement annuel — contact: jean@client.fr"
}

Ces données custom sont accessibles dans le code protégé via context.licence.custom["seats"]. Elles permettent d'adapter le comportement de l'application selon le contrat sans recompiler.

Signature ECDSA intégrée

La signature est calculée sur le payload JSON canonicalisé (clés triées alphabétiquement, sans le champ signature lui-même) :

import json, base64
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes

def sign_licence(data: dict, private_key) -> dict:
    payload = {k: v for k, v in data.items() if k != 'signature'}
    canonical = json.dumps(payload, sort_keys=True, separators=(',',':')).encode()
    sig = private_key.sign(canonical, ec.ECDSA(hashes.SHA384()))
    data['signature'] = base64.b64encode(sig).decode()
    return data

Parsing sécurisé Python côté client

def load_licence(path: str, public_key) -> dict:
    with open(path, encoding='utf-8') as f:
        data = json.load(f)

    # 1. Vérifier la version du format
    if data.get('format_version') != '2.0':
        raise ValueError("Format de licence non supporté")

    # 2. Vérifier la signature ECDSA
    sig_b64 = data.pop('signature')
    canonical = json.dumps(data, sort_keys=True, separators=(',',':')).encode()
    try:
        public_key.verify(base64.b64decode(sig_b64), canonical, ec.ECDSA(hashes.SHA384()))
    except:
        raise SecurityError("Signature de licence invalide")

    data['signature'] = sig_b64  # restaurer
    return data

Validation complète

from datetime import datetime, timezone
import sys

def validate_licence(lic: dict, current_hw_id: str, product: str):
    now = datetime.now(timezone.utc)

    # Produit
    if lic['product'] != product:
        sys.exit("Licence invalide pour ce produit")

    # Machine
    if lic['hardware_id'] != current_hw_id:
        sys.exit("Licence non autorisée sur cette machine")

    # Expiration (avec grâce)
    expires = datetime.fromisoformat(lic['expires_at'].replace('Z','+00:00'))
    grace = lic.get('grace_days', 0)
    from datetime import timedelta
    if now > expires + timedelta(days=grace):
        sys.exit("Licence expirée")

Conclusion

Le format .lic IronLock v2 est minimal mais extensible : les champs obligatoires garantissent la sécurité de base, le champ features permet la gestion des éditions, et le champ custom libre permet d'embarquer n'importe quelle donnée métier dans la licence — sans recompiler le loader. La signature ECDSA P-384 rend toute falsification détectable immédiatement.

🔐
PRODUIT LIÉ
IronLock v2.0
Découvrir IronLock → Manuel complet
← Retour au blog Article suivant →