Apertura a breve — gli autori tengono il 100 % del loro prezzo Diventa autore Accedi Crea un account
Ciao, accedi Account ed elenchi AreaAutore

Licenze, documentazione per sviluppatori

Come verificare una chiave di licenza dentro il tuo prodotto: la libreria, l’API, integrazioni pronte per undici ambienti e le regole che tengono in funzione i siti dei tuoi acquirenti.

1.Che cosa fa, e che cosa non fa

Una chiave di licenza lega il tuo prodotto a un dominio, o a quanti ne ha pagati l’acquirente. Permette a un cliente onesto di restare dentro ciò che ha comprato, ti dà il conteggio delle tue installazioni e rende una chiave trapelata eliminabile: il proprietario la rigenera e quella trapelata muore. Non è una protezione contro la copia. Il tuo acquirente ha i tuoi sorgenti e può togliere il controllo in cinque minuti. Chi ti dice il contrario ti sta vendendo qualcosa.

2.Tre passaggi

  1. Apri il tuo prodotto nella tua area autore, vai su Licenza e scarica la libreria. Contiene già la nostra chiave pubblica e l’identificativo del tuo prodotto: non c’è nulla da configurare.
  2. Chiedi la chiave all’acquirente alla prima installazione e salvala dove il tuo prodotto conserva le sue impostazioni. Non scriverla mai nel codice.
  3. Chiama valide() dove conta. Una volta per richiesta basta: la libreria usa la cache e non va in rete a ogni pagina.

3.La tua libreria non è quella di nessun altro

Il file che scarichi è generato per un prodotto. La classe, il nome del file e il file di cache portano tutti un nome derivato da quel prodotto, e l’indirizzo chiamato viene assemblato a runtime anziché scritto in un pezzo solo. Due dei tuoi stessi prodotti non condividono un solo identificativo.

Siamo chiari su cosa ti dà. Chi apre i tuoi file e li legge trova il controllo in due minuti: questo non è cambiato e non cambierà mai. Quello che ferma è l’altra cosa: uno script che cerca un nome di classe noto in migliaia di prodotti e lo toglie da solo. È così che le copie nulled si producono in serie, e qui non funziona più. Il costo di piratare il tuo prodotto passa da niente a un esame manuale, prodotto per prodotto.

Gli esempi di questa pagina chiamano la classe Licence. Nel tuo download porta il suo vero nome, mostrato accanto al pulsante di download nella tua area autore.

4.L’integrazione, ambiente per ambiente

Apri quello che corrisponde al tuo prodotto. Ogni blocco è completo e pronto da incollare; rinomina monmodule e adatta la conservazione della chiave alle tue impostazioni. Gli ambienti PHP usano la libreria scaricabile; gli altri mostrano il client completo, cache e verifica della firma incluse.

PHP PHP senza framework SDK PHP 5.6 → 8.4

La forma più breve. Metti il controllo all’ingresso di ciò che proteggi: un front controller, una pagina di amministrazione, uno script pianificato.

monproduit.php
require_once __DIR__ . '/Licence.php';

$licence = new Licence(
    $config['licence'],
    __DIR__ . '/cache'
);

if (!$licence->valide()) {
    http_response_code(403);
    exit($licence->message());
}
PS PrestaShop 1.7 e 8 SDK 1.7 · 8 · 9

Tieni una sola istanza per l’intero modulo. Controlla in getContent() perché il commerciante veda il motivo del rifiuto nella schermata di configurazione, e anche negli hook di visualizzazione perché un modulo configurato a metà non compaia in negozio.

monmodule.php
require_once dirname(__FILE__) . '/Licence.php';

class MonModule extends Module
{
    private $licence = null;

    private function licence()
    {
        if ($this->licence === null) {
            $this->licence = new Licence(
                Configuration::get('MONMODULE_LICENCE'),
                _PS_CACHE_DIR_ . 'monmodule'
            );
        }
        return $this->licence;
    }

    public function getContent()
    {
        if (Tools::isSubmit('submitLicence')) {
            Configuration::updateValue('MONMODULE_LICENCE', Tools::getValue('licence'));
        }

        $l = $this->licence();

        if (!$l->valide()) {
            return $this->displayError($l->message()) . $this->formulaireLicence();
        }
        if ($l->essai()) {
            return $this->displayWarning($this->l('Development licence')) . $this->contenu();
        }

        return $this->contenu();
    }

    public function hookDisplayHeader()
    {
        if (!$this->licence()->valide()) {
            return '';
        }
        return $this->contenuFront();
    }
}

Verificato su 1.7, 8 e 9. Le chiamate usate — Module, Configuration, Tools, _PS_CACHE_DIR_ — hanno attraversato le rimozioni della 9.0 senza essere toccate. Le deprecazioni annunciate per la 10.0 riguardano i controller di amministrazione, che questo esempio non usa.

WP WordPress SDK 5.0 → 7.1

Un’istanza statica in una funzione, un avviso in amministrazione e il corpo del plugin dietro il controllo. Non eseguire il controllo sul front di ogni pagina: la cache lo rende economico, ma un avviso sta in amministrazione.

monplug.php
require_once __DIR__ . '/Licence.php';

function monplug_licence() {
    static $l = null;
    if ($l === null) {
        $l = new Licence(
            get_option('monplug_licence'),
            WP_CONTENT_DIR . '/cache/monplug'
        );
    }
    return $l;
}

add_action('admin_notices', function () {
    $l = monplug_licence();
    if (!$l->valide()) {
        printf('<div class="notice notice-error"><p>%s</p></div>', esc_html($l->message()));
    } elseif ($l->essai()) {
        echo '<div class="notice notice-warning"><p>'
           . esc_html__('Development licence', 'monplug') . '</p></div>';
    }
});

add_action('init', function () {
    if (monplug_licence()->valide()) {
        monplug_demarrer();
    }
});

Le cinque chiamate usate — add_action, add_filter, get_option, esc_html, WP_CONTENT_DIR — non sono cambiate da WordPress 5.0 e funzionano ancora sul ramo 7.1. Niente qui dipende dall’editor a blocchi o da una rotta REST.

Per WooCommerce, condiziona ciò che aggiungi al negozio anziché l’intero plugin: un metodo di pagamento che sparisce è più chiaro per il commerciante di un plugin che muore in silenzio.

woocommerce.php
add_filter('woocommerce_payment_gateways', function ($passerelles) {
    if (monplug_licence()->valide()) {
        $passerelles[] = 'WC_Gateway_MonPlug';
    }
    return $passerelles;
});

add_action('woocommerce_admin_field_monplug_licence', function () {
    $l = monplug_licence();
    if (!$l->valide()) {
        echo '<div class="error inline"><p>' . esc_html($l->message()) . '</p></div>';
    }
});
M2 Magento 2 SDK 2.4

Avvolgi la libreria in un model e iniettalo dove serve. La cartella di cache deve essere quella di Magento, altrimenti un deploy cancellerà il tuo verdetto a ogni rilascio.

Model/Licence.php
namespace Editeur\MonModule\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Filesystem\DirectoryList;

class Licence
{
    private $licence;

    public function __construct(ScopeConfigInterface $config, DirectoryList $dossiers)
    {
        $this->licence = new \Licence(
            (string) $config->getValue('monmodule/general/licence'),
            $dossiers->getPath(DirectoryList::CACHE) . '/monmodule'
        );
    }

    public function valide(): bool
    {
        return $this->licence->valide();
    }

    public function message(): string
    {
        return $this->licence->message();
    }
}

La libreria non ha namespace: dichiarala nel composer.json del tuo modulo invece di chiamare require_once a mano.

composer.json
{
  "autoload": {
    "files": [
      "Licence.php"
    ]
  }
}
LV Laravel SDK 9 → 12

Un middleware è il posto giusto: registralo sul gruppo di rotte che appartiene al tuo prodotto, mai globalmente, così un problema di licenza non travolge mai il resto dell’applicazione.

app/Licence/VerifieLicence.php
namespace App\Licence;

use Closure;
use Illuminate\Http\Request;

class VerifieLicence
{
    private $licence;

    public function __construct()
    {
        $this->licence = new \Licence(
            config('monmodule.licence'),
            storage_path('app/monmodule')
        );
    }

    public function handle(Request $requete, Closure $suivant)
    {
        if (!$this->licence->valide()) {
            abort(403, $this->licence->message());
        }

        return $suivant($requete);
    }
}
SF Symfony SDK 5.3 → 7

Un event subscriber su kernel.request, che salta le sotto-richieste. Passa la chiave e la cartella di cache come argomenti nella definizione del servizio anziché leggere la configurazione dentro la classe.

src/Licence/LicenceSubscriber.php
namespace App\Licence;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;

class LicenceSubscriber implements EventSubscriberInterface
{
    private $licence;

    public function __construct(string $cle, string $dossierCache)
    {
        $this->licence = new \Licence($cle, $dossierCache);
    }

    public function onKernelRequest(RequestEvent $evenement): void
    {
        if (!$evenement->isMainRequest()) {
            return;
        }

        if (!$this->licence->valide()) {
            throw new AccessDeniedHttpException($this->licence->message());
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => ['onKernelRequest', 16]];
    }
}

isMainRequest() è arrivata in Symfony 5.3. Su una versione precedente chiama isMasterRequest(): tutto il resto è identico.

JS Node.js API Node 18+

Nessuna dipendenza oltre alla libreria standard. Questo è il client completo: risposta firmata, cache legata al proprio host, riutilizzo per 24 ore e proroga di 30 giorni, le stesse regole della libreria PHP.

licence.mjs
import { createPublicKey, verify } from 'node:crypto';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';

const API = 'https://addonsmarket.com/api/licence';
const PUBLIQUE = '/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=';
const SURSIS = 2592000;

const publique = createPublicKey({
  key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'),
                      Buffer.from(PUBLIQUE, 'base64')]),
  format: 'der',
  type: 'spki',
});

const canonique = (d) =>
  JSON.stringify(Object.fromEntries(Object.keys(d).sort().map((k) => [k, d[k]])));

const signee = (rep, hote) =>
  rep && rep.donnees && rep.signature
  && rep.donnees.hote === hote
  && verify(null, Buffer.from(canonique(rep.donnees), 'utf8'), publique,
            Buffer.from(rep.signature, 'base64'));

export async function etat(cle, hote, fichierCache) {
  const maintenant = Math.floor(Date.now() / 1000);
  let cache = null;

  try {
    const lu = JSON.parse(await readFile(fichierCache, 'utf8'));
    if (signee(lu, hote)) cache = lu;
  } catch {}

  if (cache && cache.donnees.revalider > maintenant) return cache.donnees;

  try {
    const r = await fetch(API, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'User-Agent': 'MonProduit/1.0' },
      body: JSON.stringify({ cle, url: hote }),
      signal: AbortSignal.timeout(6000),
    });
    if (!r.ok) throw new Error(String(r.status));

    const rep = await r.json();
    if (!signee(rep, hote)) throw new Error('signature');

    await mkdir(dirname(fichierCache), { recursive: true });
    await writeFile(fichierCache, JSON.stringify(rep));
    return rep.donnees;
  } catch {
    if (cache && cache.donnees.emis + SURSIS > maintenant) return cache.donnees;
    return { ok: false, motif: 'reseau' };
  }
}

Node 18 o successivo: fetch è diventato globale nella 18, e AbortSignal.timeout nella 17.3. Su un runtime precedente sostituisci questi due con https.request e un tuo timer.

Poi chiamalo una volta per richiesta, e non bloccare mai per un guasto di rete.

serveur.mjs
import { etat } from './licence.mjs';

const d = await etat(process.env.LICENCE, req.hostname, './var/licence.json');

if (!d.ok && d.motif !== 'reseau') {
  return res.status(403).json({ erreur: d.motif });
}
UI React, Vue, Angular API

Non esiste un modo onesto di verificare una licenza in un browser. Quello che scrivi gira sulla macchina dell’acquirente, in codice che può leggere, e toglierlo costa una riga negli strumenti per sviluppatori. Non forniamo una libreria front perché sarebbe venderti un placebo.

Se il tuo prodotto ha un server, controlla lì ed esponi il verdetto come dato. L’interfaccia potrà dire qualcosa di utile senza che il controllo stia nel browser.

serveur.mjs
app.get('/api/etat', async (req, res) => {
  const d = await etat(process.env.LICENCE, req.hostname, './var/licence.json');
  res.json({ actif: d.ok, essai: d.essai });
});
Licence.jsx
const [actif, setActif] = useState(null);

useEffect(() => {
  fetch('/api/etat')
    .then((r) => r.json())
    .then((d) => setActif(d.actif));
}, []);

if (actif === false) {
  return <Bandeau>Licence inactive</Bandeau>;
}

Se il tuo prodotto è un template front senza alcun server, non c’è nulla da verificare a runtime e non bisogna fingere il contrario. La licenza si applica dove può: al download e tramite le condizioni che il tuo acquirente ha accettato.

SH Shopify API

Un’app Shopify ha un proprio server: usa lì il client Node o PHP e invia il dominio del negozio, l’indirizzo myshopify.com oppure il dominio personalizzato se è su quello che ti basi. Un tema Shopify non ha server, quindi vale la sezione front qui sopra.

PY Python API Python 3.7+

Libreria standard più cryptography per la firma. Le stesse regole di sempre: riutilizzo per 24 ore, conservazione dell’ultimo verdetto per 30 giorni se taciamo, blocco solo per un rifiuto firmato.

licence.py
import base64, json, time, urllib.request
from pathlib import Path
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

API = 'https://addonsmarket.com/api/licence'
PUBLIQUE = '/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw='
SURSIS = 2592000

_publique = Ed25519PublicKey.from_public_bytes(base64.b64decode(PUBLIQUE))


def _canonique(d):
    return json.dumps(d, sort_keys=True, separators=(',', ':'), ensure_ascii=False)


def _signee(rep, hote):
    try:
        if rep['donnees']['hote'] != hote:
            return False
        _publique.verify(base64.b64decode(rep['signature']),
                         _canonique(rep['donnees']).encode('utf-8'))
        return True
    except (KeyError, TypeError, ValueError, InvalidSignature):
        return False


def etat(cle, hote, fichier_cache):
    maintenant = int(time.time())
    chemin = Path(fichier_cache)
    cache = None

    try:
        lu = json.loads(chemin.read_text(encoding='utf-8'))
        if _signee(lu, hote):
            cache = lu
    except (OSError, ValueError):
        pass

    if cache and cache['donnees']['revalider'] > maintenant:
        return cache['donnees']

    try:
        corps = json.dumps({'cle': cle, 'url': hote}).encode('utf-8')
        requete = urllib.request.Request(API, data=corps, headers={
            'Content-Type': 'application/json',
            'User-Agent': 'MonProduit/1.0',
        })
        with urllib.request.urlopen(requete, timeout=6) as r:
            rep = json.loads(r.read().decode('utf-8'))

        if not _signee(rep, hote):
            raise ValueError('signature')

        chemin.parent.mkdir(parents=True, exist_ok=True)
        chemin.write_text(json.dumps(rep), encoding='utf-8')
        return rep['donnees']

    except Exception:
        if cache and cache['donnees']['emis'] + SURSIS > maintenant:
            return cache['donnees']
        return {'ok': False, 'motif': 'reseau'}
{ } HTTP grezzo API

Per tutto il resto: Go, Ruby, .NET, Java, uno script di shell. Un POST, un corpo JSON. I campi sono nella sezione API più sotto, e ciò che devi verificare prima di fidarti della risposta nella sezione sulla firma.

curl
curl -X POST https://addonsmarket.com/api/licence \
     -H 'Content-Type: application/json' \
     -H 'User-Agent: MonProduit/1.0' \
     -d '{"cle":"VOTRE_CLE","url":"boutique.exemple.fr"}'

5.Dirlo invece di romperlo

Per un tema o uno script venduto a chi non è uno sviluppatore, spegnere il sito su una copia senza licenza è la mossa sbagliata: a soffrirne è di solito l’acquirente che ha ricopiato male la chiave, e a pagarla è la tua casella di assistenza. banniere() restituisce invece un piccolo avviso fisso: nient’altro cambia nella pagina.

index.php
require_once __DIR__ . '/Licence.php';

$reglages = @include __DIR__ . '/licence.php';

$licence = new Licence(
    is_array($reglages) ? $reglages['cle'] : '',
    __DIR__ . '/cache'
);

register_shutdown_function(function () use ($licence) {
    echo $licence->banniere('My theme');
});

L’acquirente lo fa sparire mettendo la sua chiave in un file, accanto alla libreria. Quel file è l’unica cosa che deve toccare.

licence.php
<?php

return array(
    'cle' => '',
);

Restituisce una stringa vuota quando la licenza è valida, e anche quando la rete è caduta: un visitatore non vede mai un avviso perché il nostro server ha avuto una brutta nottata. Con una chiave di sviluppo lo dice, con discrezione, ed è esattamente ciò che vuoi mentre costruisci.

6.I prodotti senza server: temi HTML, template, grafiche

Un tema HTML, un file Figma, un set di icone, un font: niente di tutto ciò gira su un server che puoi interrogare. Tutto quello che consegni viene letto, modificato e ridistribuito da chi possiede i file. Non c’è alcun controllo da aggiungere, e non esiste una versione di questa pagina in cui le cose cambino.

Non fingere. Uno script offuscato che chiama casa da una pagina statica, un’impronta nascosta in un canvas, una build che si rompe dopo trenta giorni: tutto questo lo toglie in un pomeriggio l’unica persona che ti avrebbe piratato, e tutto questo finisce in un ticket di assistenza dei cinquanta che hanno pagato. Spenderesti la tua credibilità per non proteggere nulla.

Quello che esiste invece agisce alla consegna, non all’esecuzione. Ogni download viene riconfezionato per l’acquirente che l’ha richiesto, e porta tre cose:

  • Un file di licenza nella radice dell’archivio, che nomina l’ordine, il titolare e la portata della licenza.
  • Un’impronta, in quel file e nel commento dell’archivio: venti caratteri derivati dalla licenza e dal prodotto, che identificano quel download e nessun altro.
  • Una frase, in due lingue, che dice quanto costa ridistribuirlo: i diritti d’uso e gli aggiornamenti, senza rimborso.

È tracciabilità, non prevenzione, e non te la venderemo per altro: un acquirente che cancella il file e svuota il commento se ne va con una copia pulita. Quello che ti dà è un nome quando una copia spunta dove non dovrebbe, un motivo perché quell’acquirente ci pensi due volte, e le due cose che una copia piratata non ha mai: gli aggiornamenti e la tua assistenza.

Qui conta una distinzione. Se il tuo tema contiene del PHP — un modulo di contatto, un file di configurazione, un include — allora gira su un server, il controllo qui sopra è reale e l’avviso funziona. Solo nel caso completamente statico, HTML e CSS e nient’altro, non esiste alcun controllo a runtime.

7.La libreria

Un solo file PHP, nessuna dipendenza, compatibile fino a PHP 5.6 per girare sulle vecchie installazioni che i tuoi acquirenti hanno ancora. Bastano quattro metodi.

new Licence($cle, $dossierCache) Costruiscila con la chiave dell’acquirente e una cartella scrivibile per la cache.
$licence->valide() Vero quando il prodotto può funzionare su questo dominio.
$licence->essai() Vero con una chiave di sviluppo o un dominio di test. Mostra un avviso discreto: non rilasciare mai una versione che lo nasconda.
$licence->message() Una frase da mostrare all’acquirente, già tradotta, che nomina i domini e data l’evento.

8.La regola che conta di più

Non lasciare mai che un problema di rete spenga il negozio del tuo acquirente. La libreria se ne occupa già e non devi aggirarla: un verdetto fresco viene riutilizzato per 24 ore; quando il nostro server non risponde, l’ultimo verdetto noto resta valido 30 giorni e il prodotto continua a funzionare; solo un rifiuto firmato lo ferma, subito. Se il nostro server ha una brutta nottata, nessun negozio si spegne. Se una chiave viene trasferita, il vecchio sito si ferma entro 24 ore.

9.I domini

Una licenza si lega al dominio registrabile. Una chiave su esempio.it copre www.esempio.it, negozio.esempio.it e qualsiasi altro sottodominio: il tuo acquirente non ti scriverà per un redirect mancante. Gli indirizzi di prova non consumano mai una licenza: localhost, .local, .test e i soliti prefissi dev., staging., preprod. funzionano senza consumare nulla.

10.L’API, se preferisci chiamarla da solo

La libreria è solo un involucro sottile attorno a un unico endpoint. Chiamalo direttamente se lavori fuori da PHP, ma allora dovrai implementare tu stesso le regole di cache qui sopra e verificare la firma.

Endpoint

POST https://addonsmarket.com/api/licence

Richiesta

HTTP
POST /api/licence HTTP/1.1
Host: addonsmarket.com
Content-Type: application/json
User-Agent: MonProduit/1.0

{
  "cle": "VOTRE_CLE",
  "url": "boutique.exemple.fr"
}

Che cosa invii

cle string La chiave dell’acquirente. Solo lettere e cifre; togliamo il resto, quindi spazi e trattini incollati non danno fastidio.
url string L’host su cui gira il tuo prodotto. Va bene anche un URL completo: teniamo solo l’host. Togli tu stesso la porta e il www. iniziale se vuoi che la risposta corrisponda a ciò che hai inviato.

Risposta

JSON
{
  "donnees": {
    "ok": true,
    "motif": "",
    "domaine": "exemple.fr",
    "essai": false,
    "quand": "",
    "emis": 1790687453,
    "revalider": 1790773853,
    "sursis": 2592000,
    "hote": "boutique.exemple.fr"
  },
  "signature": "4p9JXJRfcB5R/LNEQwr/cwnVv1M5R1OdCNcU…"
}

Campi della risposta

ok bool Se il prodotto può funzionare qui.
motif string Perché no, quando ok è falso. Vedi la tabella più sotto.
domaine string Il dominio a cui è legata la licenza.
essai bool Chiave di sviluppo o dominio di test.
quand string Quando la chiave è stata sostituita o revocata.
emis int Quando abbiamo risposto. L’inizio della proroga di 30 giorni.
revalider int Marca temporale prima della quale non devi richiedere.
sursis int Secondi per cui puoi conservare questo verdetto se smettiamo di rispondere.
hote string L’host su cui abbiamo risposto. Rifiuta la risposta se non è il tuo.

Codici HTTP

200 Un verdetto firmato. Attenzione: anche un rifiuto è un 200 — leggi ok, non il codice di stato.
400 Chiave o host mancante.
405 Qualsiasi cosa diversa da POST o OPTIONS.
429 Limite di chiamate raggiunto. Conserva il verdetto in cache e riprova più tardi: non trattarlo mai come un rifiuto.

Trenta chiamate al minuto per indirizzo IP. Rispettando le regole di cache, un negozio fa una chiamata al giorno: raggiungerai questo limite solo da un test di carico o da un ciclo che ha dimenticato la cache.

Invia uno User-Agent che nomini il tuo prodotto. Alcuni agenti predefiniti di librerie HTTP, come quello di urllib in Python, vengono filtrati prima di arrivarci e ricevono un 403. Qualsiasi stringa personalizzata lo evita.

11.Perché la risposta è firmata

Senza firma, una riga nel file hosts di un server basta a rispondere «licenza valida» al posto nostro. Ogni risposta porta una firma Ed25519 sulla forma canonica dei dati: chiavi ordinate, JSON compatto. Verificala prima di fidarti di qualsiasi cosa. La libreria lo fa per te, anche sulla copia in cache, a sua volta legata al proprio host perché non la si possa spostare da un sito all’altro.

12.Verificare la firma da solo

Serve solo se chiami l’API direttamente. Ricostruisci la forma canonica dall’oggetto analizzato — mai dai byte grezzi ricevuti, che possono differire — e poi verifica la firma Ed25519 staccata con la nostra chiave pubblica.

La nostra chiave pubblica

Ed25519, 32 byte grezzi in base64. È la stessa per tutti gli autori e tutti i prodotti.

/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=

La forma canonica

La firma copre l’oggetto donnees serializzato secondo queste quattro regole, e nient’altro. Sbagliane una e ogni firma sembrerà non valida.

  1. Chiavi ordinate in modo crescente.
  2. Nessuno spazio: né dopo i due punti né dopo le virgole.
  3. Le barre non vengono sottoposte a escape.
  4. I caratteri non ASCII restano in UTF-8, senza escape barra rovesciata-u.

Per la risposta qui sopra, i byte firmati esattamente:

utf-8
{"domaine":"exemple.fr","emis":1790687453,"essai":false,"hote":"boutique.exemple.fr","motif":"","ok":true,"quand":"","revalider":1790773853,"sursis":2592000}
PHP PHP
sodium
function signature_valide(array $donnees, $signature, $publique)
{
    ksort($donnees);
    $corps = json_encode($donnees, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

    return sodium_crypto_sign_verify_detached(
        base64_decode($signature, true),
        $corps,
        base64_decode($publique, true)
    );
}
JS Node.js
node:crypto
import { createPublicKey, verify } from 'node:crypto';

const publique = createPublicKey({
  key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'),
                      Buffer.from('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=', 'base64')]),
  format: 'der',
  type: 'spki',
});

const canonique = (d) =>
  JSON.stringify(Object.fromEntries(Object.keys(d).sort().map((k) => [k, d[k]])));

const valide = verify(null, Buffer.from(canonique(rep.donnees), 'utf8'),
                      publique, Buffer.from(rep.signature, 'base64'));
PY Python
cryptography
import base64, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

publique = Ed25519PublicKey.from_public_bytes(base64.b64decode('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw='))

canonique = json.dumps(rep['donnees'], sort_keys=True,
                       separators=(',', ':'), ensure_ascii=False)

publique.verify(base64.b64decode(rep['signature']), canonique.encode('utf-8'))

13.Motivi di rifiuto

autre_domaine La chiave è valida ma appartiene a un altro dominio. Di’ all’acquirente di trasferirla dal suo account.
transfert La chiave è stata spostata su un altro sito e sostituita. L’acquirente ne ha una nuova.
regeneration L’acquirente ha chiesto una nuova chiave. La vecchia è morta definitivamente.
revocation Abbiamo revocato la chiave: abuso, frode o una controversia decisa.
expiree Una chiave di prova a tempo che è scaduta.
inconnue Questa chiave non esiste. Di solito un errore di battitura.
reseau Non siamo stati raggiungibili e non resta alcun verdetto in cache. Non bloccare per questo.

14.Provare prima di vendere

La tua area autore ti dà una chiave di sviluppo per ciascuno dei tuoi prodotti. Funziona su qualsiasi dominio, apre solo i tuoi prodotti e segnala sempre essai: un’installazione che la usa mostra quindi un avviso di sviluppo e non può mai passare per una copia venduta.

Per vedere come si comporta il tuo prodotto quando viene rifiutato, invia una chiave che non esiste. Ottieni un vero rifiuto firmato, identico a quello di una chiave revocata: puoi così controllare la tua schermata di errore senza revocare nulla.

curl
curl -X POST https://addonsmarket.com/api/licence \
     -H 'Content-Type: application/json' \
     -H 'User-Agent: MonProduit/1.0' \
     -d '{"cle":"cettecleNexistePas000000","url":"boutique.exemple.fr"}'

{"donnees":{"ok":false,"motif":"inconnue", …},"signature":"…"}

15.Che cosa devi dire al tuo acquirente

Il tuo prodotto invierà al nostro server il dominio su cui gira. È un trattamento di dati personali ai sensi del diritto europeo, e il tuo acquirente deve saperlo. Dillo nella descrizione del tuo prodotto e nella tua documentazione. Conserviamo il dominio, e l’IP per dodici mesi per risolvere le controversie; nient’altro, e non ne vendiamo nulla.

Qualcosa non è chiaro?

Scrivici dalla tua area autore. Una domanda che è stato necessario porre significa di solito che a questa pagina manca un paragrafo.

Autore