RSA signed · replay resistant · per application

CovertAuth protocol v2.1

Client authorization does not trust HTTP JSON by itself. License activation, validation and optional heartbeat use short-lived, application-specific RSA proofs.

Trust model

Do not embed a trusted server API key in a distributed client. CovertAuth keeps a unique RSA-3072 private signing key on the server; the client pins only the public verification key.

HTTPS protects normal transport. The signed proof protects the authorization decision if traffic is inspected or modified by a local debugging proxy. A user who controls their executable may still patch local verification logic; client binary hardening remains the publisher's responsibility.

Application identity & opaque route

New applications receive an opaque route such as:

https://covertauth.com/a/r4f8c3e2d9a8710f4b6c3a920/v2/

The application name is not inserted into the default route. Owners may customize the route or regenerate a new random route. Changing it intentionally breaks clients still configured with the previous URL.

Clients also pin the public Application ID (ca_app_...) and signing key ID so a proof from another CovertAuth application cannot be reused.

License activation / validation

POST /a/r4f8c3e2d9a8710f4b6c3a920/v2/license/activate
Content-Type: application/json

{
  "license_key": "CA-...",
  "hwid": "WIN-...",
  "challenge": "fresh-random-base64url-value"
}

Use /license/activate when a new HWID may need to bind or self-rebind. Use /license/validate for a previously authorized client. Validation does not silently create a new device binding.

{
  "ok": true,
  "protocol": "covertauth-proof-v2",
  "proof": "header.payload.signature"
}

Both allows and ordinary license denials are signed.

What the client MUST verify

  • RSA/SHA-256 signature using the application's pinned public key.
  • typ=COVERTAUTH-PROOF, alg=RS256, protocol version 2.
  • kid equals the configured signing key ID.
  • aud equals the expected Application ID.
  • api_slug equals the configured application route.
  • Issuer matches the expected CovertAuth server.
  • Signed challenge equals the fresh challenge sent for this request.
  • Signed license digest equals SHA-256 of the exact local license key.
  • Signed HWID digest equals SHA-256 of the exact local HWID.
  • iat/exp are current and short-lived.
  • Only after all checks pass may decision=allow unlock protected behavior.
A proxy cannot turn a denial into an allow, replay an old success, swap the user's entered key for a different key, swap the HWID, or reuse a proof from another application without causing local verification to fail.

Optional secure heartbeat

Enable heartbeat per application to see currently online licensed clients.

POST /a/r4f8c3e2d9a8710f4b6c3a920/v2/heartbeat
Content-Type: application/json

{
  "license_key": "CA-...",
  "hwid": "WIN-...",
  "challenge": "a-new-random-challenge-every-time"
}

The response is another signed proof with purpose=heartbeat. On success the server updates that license/HWID's last heartbeat and online_until. The dashboard considers the client online only until that timeout expires.

Heartbeat cannot create a device binding. If binding is enabled and the HWID has not been authorized, the heartbeat is denied according to the current license/rebind policy.

Observed HWIDs vs bound devices

CovertAuth records the HWID presented by a valid license during activation/validation even when Bind HWID is off. This creates an observed client, not a binding.

  • Observed: exact HWID encrypted at rest, HMAC fingerprint for lookup, first/last seen, IP, endpoint, result and heartbeat presence.
  • Bound: consumes one of max_hwid_bindings and participates in reset/rebind enforcement.

This separation lets you see where a key is being used without forcing a lock.

Keys, reveal & regeneration

New license keys and trusted server API keys keep their normal one-way authentication hash plus an encrypted-at-rest copy so the owner can reveal/copy them later from the dashboard.

Regenerating either type invalidates the previous value immediately. Keys created before v2.1 may be hash-only; those old plaintext values cannot be reconstructed, so regenerate them if you want a revealable copy.

RSA application private signing keys are different. They are intentionally never shown. Clients only receive the public key. Use signing-key rotation rather than exporting the private key.

Optional Discord event notifications

Each application can configure separate Discord webhook integrations and choose exactly which events should be delivered. Examples include user login success/failure, wrong HWID, registration, license activation/denial, binding/rebinding, pause/ban/revoke/resume, license-key regeneration and heartbeat online/offline/denied/wrong-HWID.

Discord URLs are encrypted at rest and events go through a server-side delivery queue. Run database/worker_discord.php from cron. Routine successful heartbeat intervals are not sent as repeated Discord messages; online/offline events are emitted once per presence transition.

Privileged server API

ca_sk_... keys are grouped by application in the dashboard and are for trusted backend infrastructure only.

POST /api/v1/admin/licenses.php
Authorization: Bearer ca_sk_SERVER_ONLY
Content-Type: application/json

{
  "application_id": "ca_app_...",
  "action": "create",
  "label": "Customer 1042",
  "bind_hwid": true,
  "max_hwid_bindings": 2,
  "rebind_after_seconds": 86400
}

To replace a license's raw key without changing the license record/settings, use action=regenerate_key with its license_id.

Legacy endpoints

/api/v1/license/validate.php and /api/v1/license/activate.php remain disabled. They return HTTP 410 so an unsigned license protocol cannot accidentally be shipped.

The existing username/password v1 APIs remain for compatibility, but they should not be used as the sole signed desktop licensing decision.