Documentation
¶
Overview ¶
Package licverify is the product-side license verification library. It has no dependency on the license server; products embed the issuer's public keys and verify licenses fully offline.
Index ¶
- Constants
- Variables
- func Eval(text string, keys map[string]ed25519.PublicKey) (State, *Payload)
- func EvalAt(text string, keys map[string]ed25519.PublicKey, now time.Time) (State, *Payload)
- func Fingerprint() (string, error)
- func FingerprintFrom(identity string) string
- func ParsePublicKey(b64 string) (ed25519.PublicKey, error)
- func TrialRemaining(firstRun int64, now time.Time) time.Duration
- func VerifyBound(p *Payload, localFP string) error
- type Customer
- type Guard
- type GuardConfig
- type Payload
- type Snapshot
- type State
Constants ¶
const EnvInstanceID = "OPENBKN_INSTANCE_ID"
EnvInstanceID overrides all hardware sources when set.
const GracePeriod = 30 * 24 * time.Hour
GracePeriod is how long after expiry the product keeps features enabled (and the server still accepts renewal). Shared constant so offline product behavior and server renewal policy agree.
const TrialPeriod = 30 * 24 * time.Hour
TrialPeriod is how long a fresh, never-licensed deployment runs before the activation prompt turns persistent. Nothing is disabled when it elapses.
Variables ¶
var ( ErrNoFingerprint = errors.New("licverify: no stable instance identity found; set " + EnvInstanceID) ErrFingerprintMismatch = errors.New("licverify: license is bound to a different instance") )
var ( ErrMalformed = errors.New("licverify: malformed license") ErrUnknownKey = errors.New("licverify: unknown signing key") ErrBadSig = errors.New("licverify: signature verification failed") ErrExpired = errors.New("licverify: license expired") ErrNotYet = errors.New("licverify: license not yet valid") )
Functions ¶
func Eval ¶
Eval judges a license file for gating decisions: valid / grace / fallback-to-community / invalid. Unlike Verify it never returns an error for expiry — expiry is a state, not a failure.
func Fingerprint ¶
Fingerprint computes this machine's instance fingerprint (fp_ + 16 hex). Deterministic per machine; different across machines and products with a different salt version.
func FingerprintFrom ¶
FingerprintFrom derives a fingerprint from a caller-supplied stable identity (e.g. a cluster UID a product resolved itself).
func ParsePublicKey ¶
ParsePublicKey decodes a base64 (std, raw or padded) Ed25519 public key, as served by the issuer's /api/keys endpoint.
func TrialRemaining ¶ added in v0.4.0
TrialRemaining is how much of the trial window is left (0 once elapsed).
func VerifyBound ¶
VerifyBound checks an activated license against the local fingerprint. Licenses not yet bound (empty hw_fingerprint) pass — binding happens at activation. Returns ErrFingerprintMismatch for a copied license.
Types ¶
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
func NewGuard ¶
func NewGuard(cfg GuardConfig) (*Guard, error)
NewGuard validates the config, fills defaults, and performs the first evaluation synchronously so State() is meaningful immediately.
func (*Guard) Refresh ¶
Refresh re-evaluates the license locally WITHOUT any network renewal, and returns the fresh snapshot. Safe to call from the constructor and right after importing a new .lic — it never blocks on the license server.
func (*Guard) RenewNow ¶
RenewNow forces one renewing evaluation immediately (the same work a Run tick does), blocking on the network if a renewal is due. Products can call it to renew on demand; Run calls it on its schedule. Unlike Refresh, this may block — do not call it from a latency-sensitive path.
type GuardConfig ¶
type GuardConfig struct {
// Keys are the embedded verification public keys (kid → key). Required.
Keys map[string]ed25519.PublicKey
// Load returns the current license text (from file, DB, …). Required.
Load func() (string, error)
// Store persists a replacement license after a successful renewal.
// Required when RenewURL is set.
Store func(text string) error
// RenewURL is the license server's renew endpoint
// (…/api/licenses/renew). Empty disables auto-renew (pure offline
// deployments carry a full-contract certificate instead).
RenewURL string
// InstanceFP defaults to Fingerprint() of this machine.
InstanceFP string
// FirstRun returns the unix timestamp this deployment first started,
// persisted by the product (create it on first call). It only drives the
// trial-vs-unlicensed distinction while NO license is held — nothing is
// gated on it. Nil (or 0) means "starting now", i.e. always StateTrial.
FirstRun func() int64
// Interval between re-evaluations. Default 1h; ±10% jitter is applied.
Interval time.Duration
// OnChange fires on every state transition (not on steady state).
OnChange func(old, cur Snapshot)
// Logf receives operational messages (renew attempts/failures). Optional.
Logf func(format string, args ...any)
// HTTPClient defaults to a 30s-timeout client.
HTTPClient *http.Client
}
Guard is the in-process license watchdog for products: it re-evaluates the license periodically (Eval + VerifyBound), auto-renews online deployments ahead of expiry, and reports state transitions. It is deliberately NOT a separate OS daemon — enforcement must live in the product process anyway, so a killable side process would add operational surface without any security gain (client-side anti-tamper is a non-goal).
Usage:
guard, _ := licverify.NewGuard(licverify.GuardConfig{
Keys: keys,
Load: func() (string, error) { return os.ReadFileString(licPath) },
Store: func(t string) error { return os.WriteFile(licPath, []byte(t), 0o600) },
RenewURL: "https://license.openbkn.ai/api/licenses/renew",
OnChange: func(old, new licverify.Snapshot) { gate.Apply(new) },
})
go guard.Run(ctx)
...
if guard.State().State == licverify.StateValid { ... }
type Payload ¶
type Payload struct {
LicID string `json:"lic_id"`
Kid string `json:"kid"`
Edition string `json:"edition"`
Customer Customer `json:"customer"`
IssuedAt int64 `json:"issued_at"`
// ExpiresAt is the technical validity of this single license (the only
// time the product enforces). 0 means never expires (community).
ExpiresAt int64 `json:"expires_at"`
// ContractExpiresAt is the contract end date, shown as "licensed until".
// Renewal never extends past it. 0 means perpetual.
ContractExpiresAt int64 `json:"contract_expires_at"`
Features []string `json:"features"`
Limits map[string]int64 `json:"limits"`
// HWFingerprint is the instance fingerprint bound at activation; empty
// until the license has been activated and reissued.
HWFingerprint string `json:"hw_fingerprint,omitempty"`
}
Payload is the signed content of a license.
func Parse ¶
Parse verifies the signature only (no expiry check) and returns the payload. The signature is verified over the exact transmitted payload bytes, so no JSON canonicalization is involved.
func Verify ¶
Verify checks the license text against the given public keys and the current time. Returns the payload on success.
func VerifyAt ¶
VerifyAt is Verify with an explicit clock, for testing and for callers that use a trusted time source.
func (*Payload) HasFeature ¶
HasFeature reports whether the license enables the named feature.
type Snapshot ¶
type Snapshot struct {
State State
Payload *Payload // nil when State is invalid
// Err explains an invalid/unusable license (gate features off).
Err error
// RenewErr reports that a background auto-renew failed while the license
// itself is still fine — do NOT gate features off on this; surface it as a
// "renewal pending" warning. Kept separate from Err so `if snap.Err != nil`
// does not disable a perfectly valid license over a transient network blip.
RenewErr error
}
Snapshot is the point-in-time judgement the product gates on.
type State ¶
type State string
State is the product-side judgement of a license file.
const ( // StateValid: signature good, within validity — full feature set. StateValid State = "valid" // StateGrace: expired less than GracePeriod ago — features stay enabled, // product should prompt for renewal. StateGrace State = "grace" // StateFallback: a commercial license expired beyond grace but its // signature still verifies. That is proof the instance was once formally // licensed: the product automatically falls back to the community // feature set, keeping data readable and exportable. StateFallback State = "fallback_community" // StateInvalid: malformed, bad signature, unknown key, or not yet valid. // The product still runs on the community feature set — a bad file is no // reason to withhold what is free anyway — but should say the license file // could not be verified rather than nag for activation. StateInvalid State = "invalid" // StateTrial: no license imported yet, within TrialPeriod of first run. // Community feature set, quietly; the product may show days remaining. StateTrial State = "trial" // StateUnlicensed: no license, trial window elapsed. Still the community // feature set — nothing is ever withheld — with a persistent prompt to // activate. StateUnlicensed State = "unlicensed" )
func TrialAt ¶ added in v0.4.0
TrialAt judges a deployment that holds no license, from the first-run timestamp the product persisted (unix seconds; <= 0 means "starting now"). Trial state is deliberately local and resettable: it exists to give new installs a quiet grace before nagging, not to gate anything. Nothing about the feature set depends on it, so there is nothing to defend.