Skip to content

Phase 08: Revocation & CRL

Implement the ability to instantly revoke, suspend, or permanently terminate licenses and individual device activations, plus generate a signed CRL that offline clients can poll.

  • src/Services/RevocationService.php

RevocationService provides explicit, guarded status-transition methods:

$svc->suspend($license_id) // → status 4 (reversible)
$svc->revoke($license_id) // → status 5 (reversible)
$svc->terminate($license_id) // → status 6 (IRREVERSIBLE)
$svc->reactivate($license_id) // status 4|5 → 1
$svc->revoke_machine($machine_id) // deactivates a single device

terminate() guards against accidentally re-activating a terminated license:

if (License::STATUS_TERMINATED === $current_status) {
return new WP_Error('terminated', 'Cannot modify a terminated license.');
}

build_crl() creates a signed CRL that clients can fetch to validate licenses offline without hitting the main validate endpoint on every check.

CRL payload:

{
"revoked_keys": ["sha256_hash_1", "sha256_hash_2"],
"revoked_fingerprints": ["hmac_fp_1"],
"generated_at": "2026-06-15T12:00:00Z",
"iat": 1750000000
}

The payload is signed with Signer::sign() so clients can verify its authenticity with the embedded public key. The signed CRL is cached in a wplm_crl transient for one hour to avoid regenerating it on every client poll.

REST endpoint: GET /wplm/v1/revocation/crl — public route, no auth required. Clients should poll this at a reasonable interval (e.g., hourly).

RevocationService::revoke_machine() optionally adds the device fingerprint to wp_wplm_blacklist, preventing re-registration with a new license on the same device.

LicenseService::validate() checks the blacklist in step 6 of its 7-step algorithm, so blacklisted fingerprints are rejected before any other validation logic.

TransitionAction fired
suspend()wplm_license_suspended
revoke()wplm_license_revoked
terminate()wplm_license_terminated
reactivate()wplm_license_reactivated
revoke_machine()wplm_machine_revoked