Skip to content

Offline verification

The offline artifact is a signed v3 lease issued by the Keylight API. The SDK reconstructs the exact signed payload (entitlements sorted, pipe-delimited) and verifies it with Ed25519 against the tenant’s trusted keyset, applying a 300-second clock-skew tolerance.

#include <keylight/keylight.hpp>
keylight::Config cfg;
cfg.tenantId = "your-tenant";
cfg.productId = "your-product";
cfg.sdkKey = "sdk_live_…";
// Pin trusted keys explicitly instead of fetching them:
cfg.trustedKeys = { { "k1", "<raw ed25519 public key, base64>" } };
cfg.maxOfflineDays = 7; // omit to run offline as long as the lease itself is current

The trusted keyset can be fetched once from GET /{tenant}/.well-known/keylight-keys (keylight::fetchKeyset) or pinned at construction time directly in Config::trustedKeys.

validate() returns the cached lease only when it is kid-known, signature-valid, unexpired, and (if set) within maxOfflineDays of the last online validation. Entitlement checks read that gated result, so they work with no network call:

if (kl.hasEntitlement("pro")) {
// reads the gated cached lease — offline, no network
}

A forged or hand-edited lease cannot pass verification without the tenant’s private key — the security boundary is the signature, not at-rest secrecy. Leases are signed, not encrypted.

state() additionally forces State::Invalid if the system clock has been rolled back past tolerance since the last server contact, closing the offline path to reviving an expired lease. The check is UTC-based (timezone changes don’t trip it) and self-heals on the next successful online validate().

You can verify a lease without a Client instance — useful in server middleware, a license validator tool, or a second-opinion check:

#include <keylight/verifier.hpp>
// `lease` is a parsed keylight::Lease.
// Build the canonical signed payload and verify directly:
auto payload = keylight::canonical_payload(lease);
keylight::Verifier verifier;
verifier.addKey("k1", "<raw ed25519 public key, base64>");
auto result = verifier.verify(lease, std::time(nullptr));
// result.kidKnown — the lease's key ID is in the trusted set
// result.signatureValid — Ed25519 signature checks out
// result.expired — lease notAfter has passed (within 300s skew)
if (!result.kidKnown || !result.signatureValid) {
// reject — forged or from an unknown key rotation
}
if (result.expired) {
// reject — lease is past its validity window
}

canonical_payload returns the exact byte sequence the Keylight API signs: entitlements are sorted lexicographically and joined with | before the Ed25519 signature is applied. This byte-for-byte format is identical across the Swift, Rust, JavaScript, and C# SDKs — verified by the cross-SDK conformance vectors.

The default FileStore writes leases to the platform-appropriate data directory (default_store_path(cfg)). The on-disk format is the raw signed JSON lease — it is not encrypted at rest. If your threat model requires at-rest secrecy, wrap FileStore with your own encrypted store by subclassing keylight::Store.