CertifiEd

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

text
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.

JSON
{  "alg": "Ed25519",  "kid": "019f6b40-0fbb-7a69-adbf-fb5f476ad4bd",  "typ": "certified-license",  "ver": 1}
FieldTypePurpose
algstringSignature algorithm, always Ed25519
kidGUIDId of the signing_key used — it selects the public key for verification
typstringType discriminator, certified-license
verintToken format version (currently 1)

Payload

JSON
{  "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}}"}
FieldTypePurpose
licGUIDLicense id
keystringHuman-readable key CFED-XXXX-XXXX-XXXX-XXXX
tplGUIDTemplate id
tplVintTemplate version number
cfgVintLicense configuration version
issstringIssuer, certified-api by default
subGUIDSubject — id of the licensee company
iatlongIssued at, Unix seconds
nbflongNot valid before, Unix seconds
explongHard expiry, Unix seconds (license.expires_at)
maxOfflineDaysintHow many days the client survives without network — never above 30
hwfpstring?Hardware fingerprint when the license is hardware-bound; otherwise omitted
cfgstringJSON string with the configuration (features / limits) per the template version schema

How the signature is built

  1. header and payload are serialized and each is base64url-encoded → headerB64, payloadB64.
  2. The message to sign is formed: the ASCII bytes of "{headerB64}.{payloadB64}" — everything before the last dot.
  3. 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 under CERTIFIED_MASTER_KEY.
  4. 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:

JSON
{  "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:

text
base64url(payload) . base64url(signature)

Payload (typ = certified-heartbeat):

JSON
{  "typ": "certified-heartbeat",  "lic": "019f6b40-89c4-7d41-be2a-4a151a3c0063",  "key": "CFED-S7FN-RJFA-KAEY-8KZC",  "iat": 1784210819,  "exp": 1784815619,  "mid": "64d519a09298303a4ac813c33b663f3e859e6b5122d0210143a6c593819cf2cf"}
FieldPurpose
typDiscriminator, certified-heartbeat
licLicense id
keyLicense key
iatIssued at, Unix seconds
expValid until = iat + maxOfflineDays, Unix seconds
midMachine 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:

text
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     — later

The 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 hwBindingnone / fixed / firstActivation. Under fixed, and under a pinned firstActivation, the server checks the fingerprint on activation and rejects a foreign one (activation.fingerprint_mismatch).
  • firstActivation carries no fingerprint at issuance: the first successful activation writes it, after which the license behaves like fixed.
  • Hardware replacement is handled on the platform — POST /api/v1/panel/licenses/{id}/rebind; the license is re-signed and the .ced has to be downloaded again.
  • Your own fingerprint goes into the SDK through CertifiEdClientOptions.HwFingerprint; the default is HwFingerprint.Get().

Covered in full in Hardware binding.

See also