Licensing protocol
A CertifiEd license is a self-contained signed token. Everything an application needs for offline verification — validity dates, feature flags, limits — sits inside the signed payload. No server is involved in verification.
Implementation: TokenSerializer, TokenHeader, TokenPayload, HeartbeatMarker (CertifiEd.Application/Licensing); signing — EncryptedFileSigner (CertifiEd.Infrastructure/Signing); client-side verification — CertifiEdLicenseClient (CertifiEd.Client).
Token format
base64url(header) . base64url(payload) . base64url(signature)Three dot-separated segments. Segment encoding is base64url without padding. The JSON inside the segments is serialized in camelCase and null fields are omitted.
Header
{ "alg": "Ed25519", "kid": "019f6b40-0fbb-7a69-adbf-fb5f476ad4bd", "typ": "certified-license", "ver": 1}| Field | Type | Purpose |
|---|---|---|
alg | string | Signature algorithm, always Ed25519 |
kid | GUID | Id of the signing_key used — it selects the public key for verification |
typ | string | Type discriminator, certified-license |
ver | int | Token format version (currently 1) |
Payload
{ "lic": "019f6b40-89c4-7d41-be2a-4a151a3c0063", "key": "CFED-S7FN-RJFA-KAEY-8KZC", "tpl": "019f6b40-0fb8-78f0-9af2-e3589de54069", "tplV": 1, "cfgV": 1, "iss": "certified-api", "sub": "019f6b40-0f9c-7efd-996b-0dadd2ed1077", "iat": 1784210819, "nbf": 1784210819, "exp": 1815746819, "maxOfflineDays": 7, "cfg": "{\"features\":[\"export\",\"api\"],\"limits\":{\"maxSeats\":25}}"}| Field | Type | Purpose |
|---|---|---|
lic | GUID | License id |
key | string | Human-readable key CFED-XXXX-XXXX-XXXX-XXXX |
tpl | GUID | Template id |
tplV | int | Template version number |
cfgV | int | License configuration version |
iss | string | Issuer, certified-api by default |
sub | GUID | Subject — id of the licensee company |
iat | long | Issued at, Unix seconds |
nbf | long | Not valid before, Unix seconds |
exp | long | Hard expiry, Unix seconds (license.expires_at) |
maxOfflineDays | int | How many days the client survives without network — never above 30 |
hwfp | string? | Hardware fingerprint when the license is hardware-bound; otherwise omitted |
cfg | string | JSON string with the configuration (features / limits) per the template version schema |
How the signature is built
headerandpayloadare serialized and each is base64url-encoded →headerB64,payloadB64.- The message to sign is formed: the ASCII bytes of
"{headerB64}.{payloadB64}"— everything before the last dot. - The message is signed with the template's Ed25519 private key (chosen by the active
signing_key). Private keys sit on disk encrypted with AES-256-GCM underCERTIFIED_MASTER_KEY. - The signature is base64url-encoded and appended as the third segment.
Client-side verification (CertifiEdLicenseClient.VerifySignature) mirrors this: take everything before the last dot as ASCII, decode the signature from the last segment, then Ed25519.Verify(publicKey, message, signature). The public key is raw 32 bytes; the API serves it as hex, and the client runs Convert.FromHexString.
The .ced file
GET /api/v1/panel/licenses/{id}/download returns a JSON envelope:
{ "licenseKey": "CFED-S7FN-RJFA-KAEY-8KZC", "token": "eyJhbGciOiJFZDI1NTE5...<three segments>...RFbKNFNZCiMLL4MlNwOrsDg", "publicKey": "7bac1e3ce8578ad859bfe001a4f4d72702d00e3d1eebdd735ebdb7145d74f35b"}When reading a .ced file the SDK accepts both this envelope (taking the token field) and a bare single-line token — you can store the file as is.
The offline heartbeat marker
A separate, shorter signed object — two sections instead of three:
base64url(payload) . base64url(signature)Payload (typ = certified-heartbeat):
{ "typ": "certified-heartbeat", "lic": "019f6b40-89c4-7d41-be2a-4a151a3c0063", "key": "CFED-S7FN-RJFA-KAEY-8KZC", "iat": 1784210819, "exp": 1784815619, "mid": "64d519a09298303a4ac813c33b663f3e859e6b5122d0210143a6c593819cf2cf"}| Field | Purpose |
|---|---|
typ | Discriminator, certified-heartbeat |
lic | License id |
key | License key |
iat | Issued at, Unix seconds |
exp | Valid until = iat + maxOfflineDays, Unix seconds |
mid | Machine hardware fingerprint (machine id), when bound |
The marker is issued by the server on activation and on every successful heartbeat (HeartbeatMarker.IssueAsync, signed with the same template key). The SDK caches it on disk next to the license file as <license>.ced.hb — written atomically via .tmp + File.Move. After a restart without network the marker is loaded from disk and keeps the license alive until its exp.
Grace period and status computation
CertifiEdLicenseStatus is derived locally by the client from the token exp and the marker:
Invalid — no token loadedExpired — now > token.exp (hard expiry)
if a marker is present: Active — now <= marker.exp GracePeriod — marker.exp < now <= marker.exp + 24 hours Expired — later
if there is no marker (offline since start),count from the last local validation (lastValidatedAt): Active — now <= lastValidatedAt + maxOfflineDays GracePeriod — one more day Expired — laterThe upshot: as long as the application periodically fetches a fresh marker (a heartbeat every heartbeatIntervalSeconds), it stays Active. Lose the network and the marker holds Active for maxOfflineDays, then one day of GracePeriod, then Expired. The token's hard exp overrides all of it.
Hardware fingerprint
HwFingerprint.Get() (CertifiEd.Client) computes a stable SHA-256 hex over the machine name, RuntimeInformation.OSDescription, the first stable MAC address and the platform machine id: MachineGuid from the registry on Windows, /etc/machine-id on Linux, IOPlatformSerialNumber on macOS. The value is cached per process.
- A license carries a binding mode
hwBinding—none/fixed/firstActivation. Underfixed, and under a pinnedfirstActivation, the server checks the fingerprint on activation and rejects a foreign one (activation.fingerprint_mismatch). firstActivationcarries no fingerprint at issuance: the first successful activation writes it, after which the license behaves likefixed.- Hardware replacement is handled on the platform —
POST /api/v1/panel/licenses/{id}/rebind; the license is re-signed and the.cedhas to be downloaded again. - Your own fingerprint goes into the SDK through
CertifiEdClientOptions.HwFingerprint; the default isHwFingerprint.Get().
Covered in full in Hardware binding.
See also
- Client SDK — embedding verification into an application.
- Quickstart — issuing a license and verifying it end to end.
- API reference — client and panel endpoints.