More
Security
The threat model behind Keyrail licensing, what each part protects against, and how to integrate it safely.
This page is about integrating Keyrail safely. For what we store and where, see Security and privacy.
What Keyrail protects against#
| Threat | Protection |
|---|---|
| Forged license or certificate | Certificates are signed with Ed25519. Your app holds only the public key; the private key stays encrypted in Keyrail, and only your account owner can export it. |
| Editing a certificate (plan, dates, entitlements) | Any change breaks the signature: invalid_signature. |
| Copying a certificate to another computer | It is bound to the machine fingerprint: wrong_machine. |
| Sharing a license key widely | Activations are limited per license; extra machines get 409 activation_limit. |
| Setting the clock back to extend an offline window | The SDK remembers the newest certificate it has seen: clock_tampered. |
| Keeping access after a refund or chargeback | The next refresh is refused and the SDK clears its cached certificate. An app kept offline keeps access at most until its current certificate expires (offlineDays). |
| Guessing license keys | 100 bits of entropy per key, and rate limits on the client API. |
| Spoofed webhooks | Provider signatures (HMAC-SHA256) are verified before processing; replays are deduplicated by event ID. |
The local cache is not a secret#
The SDKs keep the license key, the last certificate and the newest issuedAt seen in a plain JSON file in the per-user app data directory. The user can read and edit it: only the certificate signature protects it. An edited certificate fails with invalid_signature, a certificate from another machine with wrong_machine, so nobody can forge or share a license through the cache. What an edit can do: restore an older certificate of the same machine together with an older issuedAt and a clock set back, which keeps that certificate usable until its own expiresAt (at most offlineDays). That is the inherent limit of offline licensing without secure hardware storage; keep offlineDays as short as your customers allow if it matters to you.
Certificate fields are text, not markup#
licensee (the customer's name, typed by the buyer at checkout), plan and entitlements (set by you) are authentic, since they come from a signed certificate, but they are free text. Render them as text, never as HTML: in Electron or Tauri webviews use textContent (or your framework's default escaping), never innerHTML. In a Tauri app, gate paid features in Rust with the verified certificate rather than only in the webview.
What it does not protect against#
Code running on your customer’s machine can be modified. Someone can patch your binary to skip the check entirely; no licensing system prevents that, only makes it inconvenient. Keyrail makes sure there is no shortcut that works for everyone: no master key to leak, no server to emulate, no certificate to share.
If your app has server-side features (sync, cloud processing), verify the certificate on your server too, with the reference verifier or the SDK: a patched client cannot fake a certificate.
Integration checklist#
- Only a valid state unlocks the app:
state.validin Node and Tauri,Ok(certificate)fromLicense::check()in Rust. Treat every other reason, includingno_license(never activated), as unlicensed. - Embed public keys in your binary, not in a configuration file next to it, as a map (
kid→ key) so that adding a key after a rotation is a one-line change. - Keep secret API keys (
kr_sk_…) on your servers. They must never ship in an app. If one leaks, delete it withDELETE /v1/api-keys/{id}and create a new one. - Store the certificate string as received; don’t rebuild it from the parsed payload.
- The SDK cache file holds the license key (needed for silent refreshes and deactivation): keep it in the per-user app data directory, and never log it. It is user-editable: trust the verified certificate, never other values of the file.
- Display
licensee,planandentitlementsas text, never as HTML. - Show the customer a clear message for
activation_limit, with a link to the customer portal where they can free a machine. - Decide your
offlineDaysconsciously: shorter means faster revocation, longer means more tolerance of offline use. - Export the private signing keys of each product once (account owner, Export private keys on the product page) and keep the file in a vault: it lets you sign certificates without Keyrail if you ever need to. Treat it like the key it is: whoever holds it can issue licenses.
- Rotate signing keys for a suspected compromise or a planned migration. A rotation has two phases: create the pending key and ship an app update embedding it, then activate it. App versions without the new public key cannot activate any more after the activation and lose access when their cached certificate expires.
Data minimization in the SDKs#
The SDKs send the license key, the product ID, a SHA-256 fingerprint and, if you pass them, a machine name, a platform and an app version. Nothing else: no usage events, no crash reports, no hardware details. The raw machine identifier never leaves the machine.
Reporting a vulnerability#
Write to support@keyrail.tech with “security” in the subject. Please give us reasonable time to fix the issue before any public disclosure.
Need help? See Troubleshooting or write to support@keyrail.tech.