Private release previewNo live purchasesRelease details ↗

EN · ORIGINAL REPOSITORY DOCUMENT

Go service and CLI

Original English text. Commands and evidence apply to the revision and environment stated in the document.

product/licensing/go/README.md

On this page

Go licensing backend

This is the runnable backend for the independently licensed original product. ../service.py remains a test/reference prototype; it is not required at runtime. The cache core has no dependency on either service. This directory uses only the Go standard library and preserves the PHP adapter’s routes and signed claims.

Build on Linux with Go 1.24 or newer (Astro/admin extension tested with Go 1.27.1):

cd product/licensing/go
umask 077
go build -o /tmp/original-licensing .
go test -race ./...
go vet ./...

go build -buildvcs=false is useful when building an exported checkout or a worktree whose Git metadata is unavailable to the build process. A static Linux binary can be built with CGO_ENABLED=0. No Python, OpenSSL executable, SQLite, or external Go module is required to run the binary. Generate a temporary RSA key separately for local fixtures (openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048; export with openssl pkey -pubout). Private PEMs may be PKCS#1 or PKCS#8; RSA keys must be at least 2048 bits.

Local operator commands

Supply LICENSING_SIGNING_KEY using at least 32 bytes of random, server-only HMAC material. Set it privately; never commit it or deliver it to PHP. The CLI requires --database before its subcommand. Use a fresh Go JSON state path: Python SQLite files are rejected, and ownership is never silently migrated.

/tmp/original-licensing --database /tmp/original-licenses.json issue --expires <future-unix-time> --sites 1
# Feed the issued key on stdin, after independently verifying origin ownership:
/tmp/original-licensing --database /tmp/original-licenses.json approve-origin --key-stdin --site https://fixture.example
/tmp/original-licensing --database /tmp/original-licenses.json serve --port 8090 --private-key /tmp/private.pem --original-package /tmp/original-fixture.zip --version 0.1.0
# Revoke using an independent operator process while the server is running:
/tmp/original-licensing --database /tmp/original-licenses.json revoke --key-stdin

The listener is fixed to 127.0.0.1. The Go process never fetches supplied origins or external packages. ZIPs must be explicitly supplied original artifacts with a semantic version; fixtures contain synthetic original text only. No upstream package is downloaded or distributed by this backend. Private credentials, billing, public deployment and TLS termination remain unconfigured. The optional Astro portal includes authenticated operator administration with an explicit startup allowlist; see PORTAL.md. The cache and PHP licensing APIs retain their existing behavior.

API and persistence

Every POST requires precisely license_key, installation_key, site as JSON strings. Existing /activate, /deactivate, /check, /entitlement, /updates, /download-token and bearer-authenticated GET /download are preserved. Requests are bounded to 16 KiB; duplicate/unknown fields, null values and trailing JSON are rejected. All replies use Cache-Control: no-store; bearer identities are never access-logged. HTTP server timeouts and header bounds are explicit.

Origins are approved per license before activation. Hosts normalize to lower case, default HTTPS port disappears, and paths/credentials/query/fragments are rejected. Use ASCII/punycode hosts: the existing PHP client does not perform IDNA conversion, so Unicode origins are explicitly rejected rather than bound to an origin the PHP client cannot reproduce. DNS labels and ports are validated.

The first activation binds the origin to a SHA-256 installation digest. Same owner reactivation is idempotent; deactivation frees the site slot while retaining ownership. Revoked/expired licenses cannot activate, check or get updates. Bound identities receive signed inactive/revoked/expired denials; unapproved origins, unknown licenses and wrong owners receive unsigned 403. Signer/storage failures receive unsigned 503. RSA PKCS#1 v1.5 SHA-256 signs the decoded compact sorted JSON bytes; envelope fields remain unpadded Base64URL payload, sig, and kid selected through SetSigningKeyID (default "fixture-v1"). Timestamps remain integer seconds, with refresh/validity/grace windows capped at 300/3600/3600 seconds and the underlying license expiry.

Update metadata includes version, ZIP SHA-256 and an HMAC download token lasting at most 300 seconds and never beyond license expiry. The configured ZIP is read and validated once, then served as immutable startup bytes. A token must match that ZIP’s digest. Each download reloads live license, ownership, approval and activation state: operator revocation or HTTP deactivation invalidates previously issued tokens immediately for subsequent checks.

State consists only of schema version, license hashes, deadlines, limits, approved origins and installation hashes/activation state. Every operation uses an independent flock descriptor and reloads state while holding the lock, so separate operator processes and concurrent HTTP requests observe one state. Writes use a mode-0600 temporary file, file fsync, atomic rename and parent-directory fsync. Failed transaction callbacks publish no changes. The CLI uses umask 077; new state directories use 0700. Schema corruption fails closed. Limits are 10,000 licenses, 100,000 approved origins, 16 MiB persisted JSON and 64 MiB ZIP. The site limit is 1..1000. This is intentionally a small local backend: state reads and mutations serialize, and whole-state rewrites trade simplicity for limited capacity. Use a local filesystem with working flock/rename/fsync; shared network filesystems are unsupported. A storage error after rename can report failure despite a committed snapshot, so retries must account for that ambiguity.

Evidence and remaining gates

Focused Go tests cover canonical signature bytes and integer JSON timestamps, signed denial states, expiry-capped grace, 16 concurrent stores competing for one slot, retained ownership, live cross-instance revocation, restart readback, mode-0600 state, aborted-transaction rollback, corruption, bounded identities, token tampering and immutable ZIP bytes. HTTP tests preserve routes/status codes, strict JSON and unsigned signer outage behavior.

The opt-in real HTTP PHP test starts a loopback Go handler and runs the existing PHP adapter in an already installed PHP Docker image. It creates temporary keys, state and a synthetic ZIP, checks activation, PHP RSA verification, metadata, byte-identical download, offline grace/expiry and live signed revocation/old-token rejection, then removes its containers and temporary fixture files:

LICENSING_PHP_DOCKER_IMAGE=<existing-php-image> go test -race -v ./...

That fixture uses Docker host networking (Linux) and creates two short-lived CLI containers; normal go test skips it. It does not prove production TLS, billing, admin authorization, disaster recovery or service capacity. Existing WordPress admin tests and a separate actual WordPress/Go integration fixture establish the admin behavior. Signing rotation and conservative reconciliation now provide explicit signer IDs and a recovery step using an operator-acknowledged authoritative snapshot. Operator ownership evidence/recovery, migration tooling, key custody/distribution, authority freshness, backups/auditing, authenticated issuance/renewals/refunds and a public HTTPS service remain operational gates.