Skip to main content

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 yabanin binary 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​

TriggerAction
Expiry approaching (< 14d)Generate new intermediate + issue new tokens
Suspected key leakAdd old serial to CRL, generate new
Quarterly cadenceProactive 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​

CommandPurpose
gen-root --key-file=XGenerate 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.