License package — Threat model
This page expands §10 of the design spec. Each row covers one threat class, the mitigation implemented in the package, and the residual risk.
Threat matrix
| # | Threat | Mitigation in this package | Residual risk / out of scope |
|---|---|---|---|
| 1 | License forgery — attacker constructs a valid-looking license without the private key | Ed25519 signature (256-bit security, deterministic). Verify rejects any body whose signature does not match. | Compromised private key. Mitigation: key rotation (Step 7 in workflow.md), air-gapped issuance (future: HSM). |
| 2 | Field tampering — attacker modifies Subject, NotAfter, or any other field after issuance | Signature covers the entire canonical JSON body. A single changed byte invalidates the signature. | None within this package. |
| 3 | Replay across audiences — attacker uses a rshell license in memscan-server | aud field is signed. WithAudience(...) rejects the license if the caller-provided audience is not in the signed list. | An operator who issues with an empty aud (wildcard). The package warns but does not reject. |
| 4 | Cross-binary reuse — attacker copies a license from one binary to another | BinarySHA256 pins the exact on-disk hash. IdentitySHA256 pins the embedded identity bytes. WithBinaryPinning() enables both checks. | Attacker simultaneously replaces both the binary and its embedded identity blob. Mitigation: pack + code-sign the binary. |
| 5 | Stale-cache substitution — attacker replaces the local revocation-list cache with an older copy | Revocation list carries a monotonic Sequence. Verify rejects any fetched list whose sequence regresses below LastSeenSequence in the state file. | State file deletion. The package resets state to zero and logs a warning; the attacker gains one grace period window. |
| 6 | Revocation server downtime — attacker takes the revocation server offline to prevent revocation delivery | Signed ExpiresAt in the revocation list. After LastFetchOk + gracePeriod expires the binary stops running (causeRevocationStale). | Operator must choose a grace period short enough to match the security model. Default: none (operator configures). |
| 7 | Password brute-force — attacker iterates passwords against the binding hash | Argon2id (64 MiB, t=3, p=4, 32-byte output). ~100 ms per attempt on 2024 hardware; GPU acceleration still bottlenecked by memory requirements. | Offline attack against the full PEM file. Mitigation: keep license files off publicly accessible paths. |
| 8 | Side-channel binding discrimination — attacker determines which constraint failed to guide enumeration | ErrLicenseInvalid is a single opaque sentinel. The internal 17-value cause enum is never serialised into the error or the PEM file. | Timing side-channels between fast checks (audience) and slow checks (argon2id). The package does not add artificial delays. In practice argon2id dwarfs all other step timings. |
| 9 | Clock rollback — attacker sets the system clock backward to bypass NotAfter | TrustedFloor = max(observed heartbeat times, observed revocation ServerTime). Verify rejects if now < TrustedFloor - skew. | Air-gapped machine that never contacts the online checks retains only LastSeenLocal monotonicity. No TPM anchor. |
| 10 | Algorithm confusion — attacker substitutes a signature generated by a different algorithm | Domain separation: the signed payload is `"maldev-license-v1\x00" | |
| 11 | Key rotation gap — operator removes old public key before old licenses expire | Trusted is a map; old keys remain valid as long as the operator keeps them present. The workflow documents the retention rule. | Human error. There is no automated key-expiry warning. |
| 12 | Heartbeat nonce replay — attacker captures a heartbeat reply and replays it | Heartbeat reply carries a random 16-byte nonce echo. Verify performs subtle.ConstantTimeCompare(reply.NonceEcho, nonce). A captured reply has the wrong nonce for the next call. | TOCTOU: an attacker racing the live binary's heartbeat call. Mitigation: TLS on the heartbeat endpoint. |
| 13 | Machine ID spoofing — attacker clones the MachineGuid / /etc/machine-id of a licensed host | hostid.Local() mixes multiple OS sources via sha256. All sources must match for the fingerprint to agree. | Attacker with root/SYSTEM can rewrite all sources. Full OS compromise is out of scope. |
| 14 | State file HMAC forgery — attacker writes a crafted state file to reset TrustedFloor | HMAC key is `HKDF(license_signature | |
| 15 | DoS via oversized input — attacker supplies a 100 MB PEM file to exhaust memory | MaxLicenseSize = 16 KB. Verify rejects oversized input before any JSON parsing. | None within this package. |
| 16 | JSON parse panic — attacker supplies malformed bytes crafted to trigger a decoder panic | jsonUnmarshalStrict uses json.NewDecoder with DisallowUnknownFields. Standard library decoder; no known panic paths on adversarial input. | Future Go stdlib vulnerabilities. Mitigated by updating the Go toolchain. |
| 17 | Sealed payload decryption — attacker reads the sealed config blob in the license | seal.Seal uses X25519 ephemeral key agreement + ChaCha20-Poly1305 AEAD. Only the holder of the recipient private key can open it. | Recipient private key compromise. Out of scope for this package. |
Explicit non-goals (documented limitations)
The following threats are out of scope for v1. Each has a documented mitigation path:
| Threat | Why out of scope | Mitigation path |
|---|---|---|
| Binary anti-tamper / anti-debug | The evasion/ packages cover this independently | Pack with cmd/packer + code-signing |
| Verify bypass via binary patching | Any sufficiently motivated attacker with code execution can patch return nil into the function | Use cmd/packer obfuscation; this is the fundamental limitation of software licensing |
| Seat counting (max N machines simultaneously) | Requires a stateful server with session accounting | v2: DB-backed server with session table |
| Perfect clock anti-rollback | Would require TPM/enclave endorsement keys | v2: TPM binding; current code is best-effort offline |
| Sub-license / delegated issuance | Signature chains with inherited constraints | v2 scope |
| HSM / PKCS#11 issuance | Key stored in YubiKey or hardware module | v2: license/hsm sub-package |
See also
- Operator workflow — step-by-step key generation, issuance, revocation
- License framing tech-md — conceptual overview, vocabulary, Mermaid flow