1. Vue d'ensemble du protocole de licence
Le système de licences Codinfy repose sur un double mécanisme cryptographique :
- Requête entrante signée HMAC-SHA256 — garantit que la requête provient d'un script légitime.
- Réponse signée JWT RS256 — garantit que la réponse provient du serveur Codinfy et n'a pas été altérée.
Endpoint
POST /api/admin/validate
Accessible publiquement, rate limité, protégé HMAC.
Sécurité requête
HMAC-SHA256
Clé partagée stockée dans le script et dans tbl_config.
Sécurité réponse
JWT RS256
Clé publique RSA 4096 bits embarquée dans le script.
Principe de base : même si un attaquant intercepte la réponse, il ne peut pas la forger sans la clé privée RSA. Même s'il intercepte la requête, il ne peut pas rejouer sans la clé HMAC.
2. Signature HMAC-SHA256 — Requête entrante
Chaque requête de validation doit porter l'en-tête X-Codinfy-Signature calculé sur le corps JSON avec la clé partagée.
Structure de la requête
POST /api/admin/validate
Content-Type: application/json
X-Codinfy-Signature: {hmac_hex}
{
"purchase_code": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"domain": "monsite.com",
"product_id": 42,
"script_version":"2.1.0",
"timestamp": 1735000000
}
Calcul de la signature (PHP)
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$signature = hash_hmac('sha256', $body, $sharedSecret);
// En-tête : X-Codinfy-Signature: {$signature}
Calcul de la signature (Node.js)
const crypto = require('crypto');
const body = JSON.stringify(payload);
const sig = crypto.createHmac('sha256', sharedSecret).update(body).digest('hex');
Règle critique : la clé secrète partagée (license_shared_secret) doit être obfusquée dans le build de production. Ne jamais la stocker en clair dans un fichier de config lisible publiquement.
Côté serveur Codinfy, la comparaison est faite avec hash_equals() (constant-time) — toute divergence déclenche un rejet HTTP 403.
3. Réponse signée JWT RS256
Le serveur retourne une réponse JSON incluant un jwt_token signé avec la clé privée RSA 4096 bits.
Structure de la réponse
{
"status": "valid",
"message": "Licence active",
"support_active": true,
"support_expires_at":"2025-12-31T00:00:00Z",
"update_available": true,
"latest_version": "2.2.0",
"grace_until": null,
"jwt_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Vérification JWT (PHP)
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$publicKey = file_get_contents(__DIR__ . '/codinfy-public.pem');
try {
$decoded = JWT::decode($jwtToken, new Key($publicKey, 'RS256'));
if ($decoded->status !== 'valid') {
$this->enterGracePeriod();
}
} catch (Exception $e) {
// JWT invalide ou falsifié → grace period
$this->enterGracePeriod();
}
Vérification JWT (Node.js)
const jwt = require('jsonwebtoken');
const publicKey = fs.readFileSync('./codinfy-public.pem', 'utf8');
try {
const decoded = jwt.verify(jwtToken, publicKey, { algorithms: ['RS256'] });
// decoded.status, decoded.support_active
} catch (err) {
// JWT invalide → grace period
}
La clé publique est lisible — elle peut être incluse en clair dans le script. Elle ne permet que la vérification, jamais la signature.
4. Protection anti-replay
Codinfy rejette toute requête dont le timestamp s'écarte de plus de 5 minutes de l'heure serveur UTC.
| Champ | Type | Description |
timestamp | int Unix | Heure UTC de génération de la requête en secondes. |
Bonne pratique
// Toujours générer le timestamp juste avant l'envoi
$payload['timestamp'] = time();
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$signature = hash_hmac('sha256', $body, $sharedSecret);
// Envoyer immédiatement
Ne jamais pré-calculer ou mettre en cache un payload signé. Le timestamp doit être régénéré à chaque appel.
5. Grace period 72h — Mode dégradé
Quand le serveur Codinfy est inaccessible, le script doit fonctionner en mode dégradé pendant 72h avant de basculer progressivement.
Stockage local du dernier JWT valide
// PHP — cache local chiffré
$cachePath = storage_path('license_cache.enc');
$cacheData = [
'status' => $decoded->status,
'grace_until' => now()->addHours(72)->toIso8601String(),
'cached_at' => now()->toIso8601String(),
];
file_put_contents(
$cachePath,
encrypt(json_encode($cacheData))
);
Logique de fallback
function checkLicense(): string
{
try {
$response = $this->callCodinfyApi();
$this->saveCache($response);
return $response->status;
} catch (NetworkException $e) {
$cached = $this->loadCache();
if ($cached && now()->lt($cached['grace_until'])) {
return 'grace'; // Mode dégradé OK
}
return 'expired'; // 72h dépassées
}
}
Tableau des états de grace
| État | Comportement attendu |
| valid | Fonctionnement normal complet. |
| grace | Fonctionnement normal, alerte discrète dans le back-office. |
| grace > 48h | Mode lecture seule : désactiver les actions destructives, avertissement visible. |
| grace > 72h | Désactivation progressive. Jamais de crash ou suppression de données. |
6. Kill switch progressif
Le kill switch Codinfy suit une progression en 3 niveaux pour ne jamais bloquer un utilisateur légitime par erreur réseau ou serveur.
Niveau 1 — Avertissement
Statut suspended ou grace < 48h. Afficher une bannière dans le back-office du script.
if ($status === 'suspended') {
$this->showAdminBanner(
'Licence suspendue — support'
);
}
Niveau 2 — Lecture seule
Grace 48–72h ou statut invalid persistant. Désactiver les opérations sensibles.
if ($status === 'readonly') {
$this->setReadOnlyMode(true);
$this->showCriticalBanner(
'Mode lecture seule'
);
}
Niveau 3 — Désactivation
72h+ sans licence valide. Rediriger vers page de réactivation sans supprimer les données.
if ($gracePeriodExpired) {
return redirect(
'/license-required'
);
}
// Ne jamais supprimer
// Ne jamais lancer fatal
Règle absolue : le script ne doit jamais crasher brutalement ni supprimer les données utilisateur à cause d'une licence. La désactivation progressive préserve toujours l'accès en lecture.
7. Vérification d'intégrité du script (checksum)
Le script calcule son propre hash SHA-256 au démarrage pour détecter toute modification non autorisée.
Calcul au build (ligne de commande)
sha256sum vendor/codinfy/license-module/src/LicenseChecker.php
# → abc123... (à embarquer dans le build)
Vérification au démarrage (PHP)
class IntegrityChecker
{
private const EXPECTED = 'abc123...'; // hash généré au build
public function verify(): bool
{
$actual = hash_file('sha256', __FILE__);
return hash_equals(self::EXPECTED, $actual);
}
}
// Bootstrap du script
if (! app(IntegrityChecker::class)->verify()) {
Log::channel('security')->critical('Integrity check failed');
$this->reportAnomaly('checksum_mismatch'); // Alerte silencieuse
}
Le module de vérification de licence doit être obfusqué dans les builds de production. Cela complique significativement le reverse engineering.
8. Rate limiting
| Dimension | Limite | Action si dépassée |
| Par adresse IP | 60 requêtes / minute | HTTP 429 + header Retry-After |
| Par domaine | 10 requêtes / minute | HTTP 429 + header Retry-After |
Gestion du 429 dans le script
$response = $client->post('/api/admin/validate', $payload);
if ($response->status() === 429) {
$retryAfter = (int)($response->header('Retry-After') ?? 60);
Cache::put('license_check_delay', now()->addSeconds($retryAfter), $retryAfter);
// Pendant l'attente, utiliser le cache local
return $this->loadCache()?->status ?? 'grace';
}
Ne jamais boucler sur les appels API en cas de 429. Respecter le Retry-After et déclencher le mode dégradé le temps de l'attente.
9. Blacklist et détection d'anomalies
Codinfy surveille automatiquement les comportements suspects et peut blacklister un domaine ou une IP via l'administration.
Seuils d'anomalies automatiques
| Signal | Seuil alerte | Seuil critique |
| Vérifications invalides | 8 / heure | 20 / heure |
| IPs uniques pour une licence | 5 | 12 |
| Débordement de domaines autorisés | 1 | — |
Réponse du serveur en cas de blacklist
{ "status": "suspended", "message": "Domaine ou IP blacklisté. Contactez support@codinfy.com." }
Traiter ce statut comme un kill switch niveau 1 (avertissement), jamais comme un crash immédiat.
10. SDK PHP — Intégration complète
Le SDK PHP officiel codinfy/license-sdk gère HMAC, JWT, cache, grace period et kill switch automatiquement.
Installation Composer
composer require codinfy/license-sdk
Utilisation minimale
<?php
use Codinfy\LicenseSdk\CodinfyLicenseClient;
$client = new CodinfyLicenseClient(
apiUrl: 'https://codinfy.com',
sharedSecret: 'votre_cle_hmac_partagee',
publicKeyPath: __DIR__ . '/codinfy-public.pem',
productId: 42,
scriptVersion: '2.1.0',
);
$result = $client->validate(
purchaseCode: $_ENV['CODINFY_PURCHASE_CODE'],
domain: $_SERVER['HTTP_HOST'] ?? 'localhost',
);
// $result->status → 'valid' | 'invalid' | 'expired' | 'suspended' | 'grace'
// $result->message → message lisible
// $result->updateAvailable → bool
// $result->latestVersion → '2.2.0'
switch ($result->status) {
case 'valid':
case 'grace':
break; // Fonctionnement normal
case 'suspended':
$this->showAdminBanner('Licence suspendue');
break;
default:
$this->activateKillSwitch($result->status);
break;
}
Configuration avancée
$client = new CodinfyLicenseClient(
apiUrl: 'https://codinfy.com',
sharedSecret: env('CODINFY_HMAC_SECRET'),
publicKeyPath: storage_path('keys/codinfy-public.pem'),
productId: 42,
scriptVersion: '2.1.0',
cacheDriver: 'file', // 'file' | 'database' | 'redis'
cachePath: storage_path('license_cache'),
gracePeriodHours: 72,
timeoutSeconds: 8,
connectTimeoutSeconds: 4,
);
11. SDK Node.js — Intégration
npm install codinfy-license-sdk
const { CodinfyClient } = require('codinfy-license-sdk');
const fs = require('fs');
const client = new CodinfyClient({
apiUrl: 'https://codinfy.com',
sharedSecret: process.env.CODINFY_HMAC_SECRET,
publicKey: fs.readFileSync('./codinfy-public.pem', 'utf8'),
productId: 42,
scriptVersion: '2.1.0',
cachePath: './storage/license_cache.json',
gracePeriodHours: 72,
});
async function boot() {
const result = await client.validate({
purchaseCode: process.env.CODINFY_PURCHASE_CODE,
domain: process.env.APP_DOMAIN || 'localhost',
});
if (!['valid','grace'].includes(result.status)) {
console.error('[Codinfy] Licence invalide — mode dégradé');
}
}
boot().catch(console.error);
12. À faire / À éviter
✔ À faire
- Recalculer le
timestamp juste avant chaque requête.
- Persister le dernier JWT valide pour le mode dégradé.
- Vérifier la signature JWT côté script avant confiance au statut.
- Respecter le
Retry-After en cas de 429.
- Obfusquer le module de vérification dans les builds de prod.
- Tester la grace period en simulant une indisponibilité réseau.
- Afficher les alertes uniquement à l'admin du script.
- Utiliser le cache local entre les appels API (30–60 min).
✘ À éviter
- Mettre la clé HMAC en dur dans un fichier texte lisible.
- Crasher l'application à cause d'une licence invalide.
- Supprimer les données utilisateur en cas de désactivation.
- Boucler sur les appels API en cas d'erreur ou 429.
- Appeler l'API à chaque requête HTTP publique.
- Ignorer les anomalies de checksum sans journaliser.
- Stocker le JWT en clair dans
public/.
- Désactiver la vérification en développement sans env dédié.