PKI Architecture for Offline License Verification
Overview
Yabanin uses an offline PKI hierarchy to manage on-premise license tokens. There is no phone-home requirement: every verification happens locally using Ed25519 cryptographic signatures.
┌───────────────────────────────────────────────────────┐
│ VENDOR (Air-Gapped) │
│ │
│ ┌─────────────────────┐ │
│ │ ROOT KEY │ Ed25519 │
│ │ (never online) │ Kept offline / HSM │
│ └─────────┬───────────┘ │
│ │ sign │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ INTERMEDIATE CERT │ Ed25519 │
│ │ + signing key │ Signed by root │
│ └─────────┬───────────┘ │
│ │ sign │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ LICENSE TOKEN │ Ed25519 │
│ │ (per-customer) │ Signed by intermediate │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ CRL (optional) │ Revoked intermediate serials │
│ └─────────────────────┘ │
└───────────────────────────────────────────────────────┘
│ deliver (USB / secure email)
▼
┌───────────────────────────────────────────────────────┐
│ CUSTOMER ON-PREM ENVIRONMENT │
│ │
│ Embedded root public key (built into binary) │
│ Intermediate cert (deployed with release) │
│ License token (per-customer JSON file) │
│ CRL (optional, deployed separately) │
│ │
│ Startup: VerifyLicenseChain() │
│ 1. Parse license token │
│ 2. Check expiry │
│ 3. Load & verify intermediate cert against root │
│ 4. Verify license sig against intermediate pubkey │
│ 5. (Optional) Check CRL for revoked intermediates │
└───────────────────────────────────────────────────────┘
Trust Model
Root Key (generate-root)
- Ed25519 keypair, never leaves the vendor's air-gapped environment.
- The public key is embedded into every production binary at build time
via
keys_gen.go(run withYABANIN_LICENSE_ROOT_PUBKEYenv var). - If compromised, the entire PKI must be re-issued.
Intermediate Certificate (issue-intermediate)
- A short-lived Ed25519 keypair (default 90 days) signed by the root.
- Used exclusively for issuing customer license tokens.
- When an intermediate expires or is suspected compromised:
- Generate a new intermediate from root.
- Publish a CRL listing the old serial as revoked.
- Deploy new cert + CRL to customers.
License Token (issue)
- Per-customer JSON token containing
customer_id,expiry,serial. - Signed by the current intermediate key.
- Delivered to the customer (USB, secure email, portal download).
- Customer places it on their server and sets env vars:
YABANIN_LICENSE_FILE— path to the JSON tokenYABANIN_LICENSE_ROOT_PUBKEY— root public key hex (64 chars)YABANIN_LICENSE_CRL_FILE— optional CRL path
CRL (crl.go)
- Optional JSON file listing revoked intermediate certificate serials.
- Used to invalidate compromised/expired intermediates without rebuilding binaries.
Key Generation & Deployment Flow
Vendor air-gap:
yabanin license gen-root --key-file=root
yabanin license issue-intermediate --root-key=root.priv --validity=90 \
--key-file=inter-2027q1 --serial=...
yabanin license issue --customer="acme-corp" --expiry=2027-12-31 \
--inter-key=inter-2027q1.priv --inter-serial=<serial> \
--output=acme-corp-license.json
Build production binary:
YABANIN_LICENSE_ROOT_PUBKEY=<root.pub hex> go generate ./pkg/license/...
make all
Deploy to customer:
Place acme-corp-license.json on server
Set env vars in systemd/docker environment
Start admin/gateway — license verified at startup
Security Considerations
| Risk | Mitigation |
|---|---|
| Root key compromise | Keep root offline; rotate intermediates regularly |
| Intermediate key theft | Short validity (90d); CRL for revocation |
| License token theft | Bind to customer org_id; expiry enforcement |
| Binary tampering | Root pubkey embedded at build time |
| Replay of old licenses | Expiry + grace period (7 days) |