Skip to content

Ed25519 leases

The Swift SDK keeps a trusted keyset per app (KeylightConfiguration.trustedPublicKeys: [String: Curve25519.Signing.PublicKey]) and verifies every lease it receives locally using Ed25519 before trusting it. Leases are a v3 pipe-delimited payload.

The signed payload includes an entitlements_csv field (sorted, comma-joined, empty string when no entitlements are granted). Sorting is required so the client and server reconstruct an identical payload regardless of the order in which entitlements were added on either side.

v3|<kid>|<keyHash>|<instanceId>|<issuedAt>|<expiresAt>|<status>|<entitlements_csv>

signed by Keylight’s Ed25519 private key. The SDK:

  1. Parses the payload.
  2. Looks up kid in trustedPublicKeys.
  3. Rejects the lease if the kid is unknown (prevents downgrade attacks and defends against compromised-key scenarios once you ship a new build).
  4. Verifies the Ed25519 signature.
  5. Checks expiresAt against the local clock (with clock-manipulation mitigation, below).

Because verification is local, entitlement survives the app being offline for the lease lifetime - typically days to weeks. Keylight is consulted only for activation, validation refreshes, and deactivation.

A user could roll their system clock back to extend an expired lease. KeylightProvider.isClockManipulated() mitigates this:

  • On every successful network round trip, the provider stores lastSeen = now in the keychain.
  • On every entitlement check, it compares now to the stored lastSeen. If now < lastSeen - 1h, the clock has moved backward by more than the drift tolerance and the lease is treated as invalid.
  • The 1-hour tolerance absorbs legitimate NTP corrections and timezone shenanigans without false positives.

This doesn’t defeat an attacker who knows to clear the keychain entry, but it stops the common “set the clock back a year” attack cold.

Keylight supports rotating your signing key when needed (suspected exposure, routine hygiene). Rotation is handled as a coordinated backend + app-side update:

  • Both the old and new public keys ship in the SDK’s trustedPublicKeys during a grace window, so users holding an unexpired lease stay .licensed through the swap.
  • New leases are signed with the new kid once Keylight flips to the new key.
  • Once adoption reaches an acceptable threshold, a later app build drops the old key.

The SDK rejects any lease whose kid isn’t in its trusted keyset, so once users are on the post-grace build, a leaked old key is worthless. There is no remote revocation of already-issued leases by design - the 7-day TTL is the revocation window.