Özel sürüm önizlemesiGerçek ödeme alınmazSürüm ayrıntıları ↗

EN · ÖZGÜN DEPO BELGESİ

İmzalı istemci protokolü

Özgün İngilizce metin. Komutlar ve kanıtlar, belgede belirtilen revizyona ve ortama aittir.

product/licensing/README.client.md

Bu sayfada

Standalone PHP entitlement client prototype

client.php is original PHP 7.2-compatible code requiring OpenSSL. It is separate from the WordPress object cache. It gates premium update access only; no cache operation, Redis connection or runtime correctness depends on licensing.

Create OriginalProduct\Licensing\EntitlementClient with license and installation secrets, an HTTPS site origin, a configured kid => RSA public PEM map and a private cache-file path. status() is strictly local. Only an explicit administrative refresh($transport) invokes your callable, with /entitlement and the request array. The entitlement transport must use the configured trusted server, verified TLS, bounded timeouts, POST JSON and a decoded response envelope. The separate WordPress admin adapter supplies explicit permission/nonce-checked actions and local-only page status. This client supplies the standalone update helpers described below.

Protocol: {payload, sig, kid}, unpadded canonical base64url. The RSA-SHA256 signature covers the decoded JSON payload bytes. Claims bind audience original-product-entitlement, normalized HTTPS origin, SHA256 license digest and SHA256 installation digest. Accepted signed statuses: active/revoked/inactive/expired. The timestamps are nonnegative integers with issued_at <= refresh_after <= expires_at <= grace_until, fresh lifetime at most 3600 seconds, refresh interval at most 300 seconds and grace at most 3600 additional seconds. Future issuance tolerance is 60 seconds. The server must cap grace at the actual license expiry; that expiry is not independently present in this envelope. The client cannot reconstruct it.

Local results: fresh/grace permit premium updates; expired/revoked/inactive/unknown do not. Expiry and grace boundaries are exclusive. A signed denial immediately replaces cached active entitlement. Older issuance or a same-time active replay following denial is rejected. Outages, malformed responses and bad signatures never extend verified deadlines. Rotation is explicit: keep old and new public keys in the configured map during transition, then remove retired key IDs.

The persisted JSON contains the signed envelope plus local observed_at high-water metadata; it stores no raw license or installation key. Atomic same-directory rename and mode 0600 protect against partial writes. New directories use 0700. Choose an existing private directory when possible, outside web-served paths. status() can persist a later observed time locally to prevent a subsequent ordinary clock rollback from renewing access; inability to retain that mark fails closed. This metadata is not signed, so a party controlling the client/files/system clock can alter enforcement. No offline entitlement system is uncrackable. Same-path local writers serialize with flock and re-read the signed envelope and highest observed time before replacement. Network transport runs outside the lock. Separate client objects observe a persisted denial on their next local status call; stale active responses cannot overwrite it. Distributed filesystems with unreliable advisory locks and externally rewritten clock metadata remain outside this prototype’s evidence. Lock or persistence failures deny local access.

Tests use generated ephemeral RSA keys; no private key is committed:

docker run --rm --network none -v "$PWD:/opt/plugin:ro" -w /opt/plugin \
  php:8.3-apache-bookworm php product/licensing/client-test.php

PHP 8.3 tests do not establish PHP 7.2 runtime coverage; the code intentionally avoids newer syntax. Cross-language compatibility with the Go replacement must pass before adopting it as the actual backend. Owner-confirmed reuse authorization is recorded in ../../PROVENANCE.md; entitlement signatures grant no upstream rights.

Explicit update helpers

checkUpdates($transport) checks the local premium entitlement, then calls $transport('POST', '/updates', identityArray, []). It requires decoded metadata with a semantic version string, lowercase SHA256 digest, bounded ASCII bearer token, active: true and the bound normalized site. download($transport, $metadata) rechecks local access and metadata, calls $transport('GET', '/download', [], ['Authorization' => 'Bearer ' . token]), then returns bytes only if their SHA256 matches. Transport must throw on non-success HTTP status, including signed-server policy denial, and use the same configured trusted HTTPS origin. No arbitrary download URL is accepted. Download bytes must be bounded by the transport to the intended package size. These helpers do not install/extract packages or modify WordPress.

Transport failures propagate; local grace cannot force a server to approve a revoked license or unavailable download. TLS/server authenticity protects unsigned update metadata; its digest detects corrupted/mismatched bytes, not a malicious issuer. No updater endpoint is configured by default.

Usable explicit HTTP transport

transport.php supplies HttpTransport, an operator-configured fixed HTTPS origin with verified TLS peer and hostname, redirects disabled, a 5-second default time budget, 64-KiB JSON and 8-MiB ZIP bounds. Configurable ceilings are 30 seconds, 1 MiB JSON and 64 MiB ZIP. Unsupported methods/paths and arbitrary headers are rejected. Non-200 responses, wrong content types, invalid JSON, TLS failures and oversized responses throw generic errors without displaying request secrets.

require_once 'product/licensing/transport.php';
use OriginalProduct\Licensing\EntitlementClient;
use OriginalProduct\Licensing\HttpTransport;

$transport = new HttpTransport(getenv('PRODUCT_LICENSE_ORIGIN'));
$keys = ['fixture-v1' => file_get_contents(getenv('PRODUCT_PUBLIC_KEY_PATH'))];
$client = new EntitlementClient(
    getenv('PRODUCT_LICENSE_KEY'), getenv('PRODUCT_INSTALLATION_KEY'),
    getenv('PRODUCT_SITE_ORIGIN'), $keys, getenv('PRODUCT_ENTITLEMENT_CACHE_PATH')
);
// Invoke these only through an explicitly authorized administrative action.
$entitlement = $client->refresh([$transport, 'entitlement']);
if ($entitlement['premium_update_access']) {
    $metadata = $client->checkUpdates($transport);
    $verifiedZipBytes = $client->download($transport, $metadata);
}
// A local status read makes no service request.
$local = $client->status();

For activation/deactivation/check, explicitly call $transport('POST', '/activate', $identity, []) (or /deactivate, /check) with license_key, installation_key, site. No lifecycle call happens automatically. Only HttpTransport::loopbackFixture('http://127.0.0.1:PORT') permits HTTP, for local fixtures. It rejects localhost names, other IPs, credentials and URL suffixes; production configuration never falls back to HTTP. Run focused validation with php product/licensing/transport-test.php; live HTTP behavior is exercised by the cross-language integration runner. PHP stream connection/header timeout behavior follows the host runtime; response-body reads also enforce the remaining time budget.