Vendor License Issuing Guide
This guide is for the vendor (Yabanin team) who controls the root key and issues licenses to on-premise customers.
⚠️ All issuing commands must be run from an air-gapped, offline machine. The root private key must never touch a networked system.
Prerequisites
- Go 1.25+ installed on the issuing machine.
- The
yabaninbinary built:make unified
Step 1 — Generate Root Keypair
# On your air-gapped machine:
./yabanin license gen-root --key-file=/secure/root
# Output (JSON):
# {
# "public_key": "a1b2c3...", # 64 hex chars (32 bytes)
# "private_key": "d4e5f6..." # 96 hex chars (seed, do NOT share)
# }
# This creates two files:
# /secure/root.pub — root public key (safe to distribute)
# /secure/root.priv — root seed (KEEP SECRET, air-gapped only)
Store the root private key in an HSM or encrypted offline storage. The public key will be embedded into production binaries at build time.
Build-time Key Embedding
When building a production binary, set the root public key as an env var:
YABANIN_LICENSE_ROOT_PUBKEY=a1b2c3d4e5f6... \
go generate ./pkg/license/... # generates pkg/license/zz_keys.go
make all # builds binaries with embedded root pubkey
The keys_gen.go tool writes zz_keys.go containing:
const VendorPublicKey = "a1b2c3d4e5f6..." // 64 hex chars
Step 2 — Create an Intermediate Certificate
Intermediates are short-lived (typically 90 days). Generate a new one before each quarter.
./yabanin license issue-intermediate \
--root-key=/secure/root.priv \
--validity=90 \
--key-file=/secure/inter-2027q1
# Output (JSON):
# {
# "version": "1",
# "serial": "aabbccdd...", # hex — record this!
# "pub_key": "...", # intermediate public key (hex)
# "root_sig": "...", # root signature over cert
# "expiry": "2027-04-01T00:00:00Z",
# "issued_at": "2027-01-01T00:00:00Z"
# }
# Creates:
# /secure/inter-2027q1.cert — intermediate cert JSON (deploy to customers)
# /secure/inter-2027q1.priv — intermediate private key (keep air-gapped)
Record the serial field. You will need it when issuing licenses and
when revoking intermediates.
When to Rotate Intermediates
| Trigger | Action |
|---|---|
| Expiry approaching (< 14d) | Generate new intermediate + issue new tokens |
| Suspected key leak | Add old serial to CRL, generate new |
| Quarterly cadence | Proactive rotation |
Step 3 — Issue a Customer License Token
./yabanin license issue \
--customer="acme-corp" \
--expiry=2027-12-31 \
--inter-key=/secure/inter-2027q1.priv \
--inter-serial=aabbccdd... \
--output=licenses/acme-corp.json
# Output (JSON):
# {
# "v": "1",
# "c": "acme-corp",
# "s": "<license serial>",
# "intermediate": "aabbccdd...",
# "e": "2027-12-31T00:00:00Z",
# "i": "2027-01-01T00:00:00Z",
# "sig": "<signature>"
# }
Deliver acme-corp.json to the customer via your preferred secure channel.
Step 4 — Revoke an Intermediate (CRL)
If an intermediate key is compromised, add its serial to the CRL:
# Create or update a CRL file:
echo '{
"revoked_serials": ["aabbccdd..."],
"updated_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}' > deploy/license/crl.json
# Deploy crl.json alongside the new intermediate cert to customers.
Customers' VerifyLicenseChain() will reject licenses if their intermediate's
serial appears in the CRL.
Quick Reference
| Command | Purpose |
|---|---|
gen-root --key-file=X | Generate root keypair |
issue-intermediate --root-key=X ... | Create intermediate cert |
issue --customer=X --expiry=Y ... | Issue per-customer license |
verify --license-file=X ... | Verify a license (for testing) |
Troubleshooting
"invalid root key seed" — the hex string is not exactly 64 characters (32 bytes). Check for whitespace or missing chars.
"intermediate cert signature invalid" — the root key used to sign does not
match the embedded root public key in the binary. Ensure --root-key matches
the key whose public key was embedded at build time.
"license expired" — the token's expiry date has passed. Generate a new token with a future expiry date.