Plugin entitlements and activation (SDK 1.0)
Package authenticity and purchase rights are separate. A .etplugin is accepted
only when its Ed25519 publisher key is trusted and its inventory verifies. A
paid product additionally needs a signed entitlement from a separately trusted
issuer. The host contains no private keys and trusts no test issuer by default.
Service contract
Request ET_SERVICE_LICENSE v1 with an et_license_service table from
<epictuner/license.h>. It uses the same bounded async request/reply structure
as the read services. All calls run on the worker dispatcher; callbacks arrive
on that dispatcher and are cancelled during stop. Operations:
| Operation | Reply |
|---|---|
ET_LICENSE_STATUS |
reply.text is compact JSON with state, allowed, reason, and features. request.id may name one feature. |
ET_LICENSE_CHALLENGE |
reply.text is the current offline challenge JSON. A new challenge replaces the previous one. |
ET_LICENSE_ACTIVATE |
Opens the host Plugins window so the user can complete online or offline activation. |
ET_LICENSE_DEACTIVATE |
Clears the cached local entitlement. |
For the licensed-feature-demo, ET_LICENSE_STATUS checks
org.epictuner.feature.pro before running the paid action. Host tune proposal,
apply, undo, redo and Burn requests from a paid plugin are checked again at
dispatch; completed host operations are not undone when a license expires.
Read-only work and panel close/export remain available. Plugin authors must
check their own protected algorithms at the point of use as well.
Signed response
The issuer signs canonical UTF-8 JSON entitlement bytes with Ed25519 over
EpicTunerEntitlement/v1\n followed by the canonical entitlement. The response
contains the original challenge, entitlement and detached signature. The
entitlement binds issuer/key ID, product/plugin, license ID, policy, features,
version range, installation ID, challenge nonce, issue time, expiry and lease.
Only the public issuer key is imported into the host. The issuer private key
remains outside the SDK, host, package and cache.
The plugin manager supports HTTPS activation endpoints and a loopback HTTP exception for the local test issuer. Offline activation exports a challenge JSON file and imports a response JSON file signed for that challenge. A response for another product, installation, nonce or issuer is rejected. Each new activation requires a new challenge. The manager reports failures without unloading the plugin or replaying work.
Policy
- Free: no entitlement or network access.
- Trial: signed expiry; no offline extension after expiry.
- Perpetual: version-bounded, available offline indefinitely while the issuer key remains trusted and the installation ID matches.
- Subscription: signed expiry is a hard stop. A signed
leaseUntilpermits 72 hours of offline grace before new restricted work is blocked. The issuer must renew the lease through a new activation before grace ends.
The host rejects a clock more than five minutes behind its last observed time. That local check cannot prevent a user who controls their machine from changing state or rolling back storage. Revoked issuer keys block cached receipts on the next status check; offline clients cannot learn server-side revocations until they reconnect or their signed lease/grace ends. Publishers must define refund, transfer, activation-limit and account-recovery policy in their issuer service.
Windows caches the signed response with DPAPI for the current user. Linux stores
the signed response in a 0600 per-user file. Neither format is a claim of
tamper-proof DRM; user-controlled native code and local account access remain
outside the plugin boundary.
Local test issuer
tools/test-issuer.py is deliberately local and uses an external Ed25519
PEM. Test keys are not shipped in the SDK or application. Example:
python tools/test-issuer.py issue --key /outside/sdk/issuer.pem \
--issuer org.example.test --key-id org.example.test.key1 \
--policy subscription --feature org.epictuner.feature.pro \
--expires 1790200000 --lease 1790120000 \
--challenge challenge.json --output response.json
For online mock activation, replace issue with serve --port 8765 and point
the Plugins window to http://127.0.0.1:8765/activate. Production activation
requires an independently deployed HTTPS issuer, revocation and recovery
operations. The mock proves the host/SDK mechanics only.