package main import ( "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/json" "errors" "fmt" "html/template" "log" "net/http" "os" "strings" "time" ) const appVersion = "1.0.0" type ActivationRequest struct { App string `json:"app"` Version string `json:"version"` GeneratedAt string `json:"generated_at"` Hostname string `json:"hostname"` Platform string `json:"platform"` Fingerprint string `json:"fingerprint"` FingerprintShort string `json:"fingerprint_short"` Components []string `json:"components"` } type SignedLicense struct { App string `json:"app"` LicenseID string `json:"license_id"` Customer string `json:"customer"` Fingerprint string `json:"fingerprint"` IssuedAt string `json:"issued_at"` ExpiresAt string `json:"expires_at,omitempty"` Features []string `json:"features,omitempty"` Signature string `json:"signature"` } type licensePayload struct { App string `json:"app"` LicenseID string `json:"license_id"` Customer string `json:"customer"` Fingerprint string `json:"fingerprint"` IssuedAt string `json:"issued_at"` ExpiresAt string `json:"expires_at,omitempty"` Features []string `json:"features,omitempty"` } type keyPairResponse struct { PublicKeyBase64 string `json:"public_key_base64"` PrivateKeyBase64 string `json:"private_key_base64"` } type generateLicenseRequest struct { PrivateKeyBase64 string `json:"private_key_base64"` App string `json:"app"` LicenseID string `json:"license_id"` Customer string `json:"customer"` Fingerprint string `json:"fingerprint"` IssuedAt string `json:"issued_at"` ExpiresAt string `json:"expires_at"` Features string `json:"features"` } type generateLicenseResponse struct { License SignedLicense `json:"license"` LicenseJSON string `json:"license_json"` PublicKeyBase64 string `json:"public_key_base64"` PayloadJSON string `json:"payload_json"` } type parseRequestResponse struct { Request ActivationRequest `json:"request"` } var pageTmpl = template.Must(template.New("index").Parse(indexHTML)) func main() { mux := http.NewServeMux() mux.HandleFunc("/", handleIndex) mux.HandleFunc("/api/health", handleHealth) mux.HandleFunc("/api/keypair", handleKeypair) mux.HandleFunc("/api/request/parse", handleRequestParse) mux.HandleFunc("/api/license/generate", handleGenerateLicense) addr := envOrDefault("ACTIVATOR_LISTEN_ADDR", ":8090") log.Printf("License activator %s listening on http://localhost%s", appVersion, addr) if err := http.ListenAndServe(addr, loggingMiddleware(mux)); err != nil { log.Fatal(err) } } func handleIndex(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } data := struct { Version string DefaultProduct string EnvPrivateKeyPresent bool }{ Version: appVersion, DefaultProduct: envOrDefault("ACTIVATOR_DEFAULT_PRODUCT", "force_monitor"), EnvPrivateKeyPresent: strings.TrimSpace(os.Getenv("LICENSE_PRIVATE_KEY_BASE64")) != "", } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := pageTmpl.Execute(w, data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func handleHealth(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r, http.MethodGet) { return } writeJSON(w, http.StatusOK, map[string]any{ "ok": true, "version": appVersion, "time": time.Now().UTC().Format(time.RFC3339), }) } func handleKeypair(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r, http.MethodPost) { return } pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, keyPairResponse{ PublicKeyBase64: base64.StdEncoding.EncodeToString(pub), PrivateKeyBase64: base64.StdEncoding.EncodeToString(priv), }) } func handleRequestParse(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r, http.MethodPost) { return } var body struct { Text string `json:"text"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, fmt.Errorf("invalid json: %w", err)) return } var req ActivationRequest if err := json.Unmarshal([]byte(strings.TrimSpace(body.Text)), &req); err != nil { writeError(w, http.StatusBadRequest, fmt.Errorf("invalid activation request json: %w", err)) return } writeJSON(w, http.StatusOK, parseRequestResponse{Request: req}) } func handleGenerateLicense(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r, http.MethodPost) { return } var req generateLicenseRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Errorf("invalid json: %w", err)) return } privateKeyBase64 := strings.TrimSpace(req.PrivateKeyBase64) if privateKeyBase64 == "" { privateKeyBase64 = strings.TrimSpace(os.Getenv("LICENSE_PRIVATE_KEY_BASE64")) } if privateKeyBase64 == "" { writeError(w, http.StatusBadRequest, errors.New("private_key_base64 is required or set LICENSE_PRIVATE_KEY_BASE64")) return } lic, payloadJSON, pubKey, err := buildSignedLicense(req, privateKeyBase64) if err != nil { writeError(w, http.StatusBadRequest, err) return } b, err := json.MarshalIndent(lic, "", " ") if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, generateLicenseResponse{ License: lic, LicenseJSON: string(b), PublicKeyBase64: pubKey, PayloadJSON: payloadJSON, }) } func buildSignedLicense(req generateLicenseRequest, privateKeyBase64 string) (SignedLicense, string, string, error) { app := strings.TrimSpace(req.App) if app == "" { app = envOrDefault("ACTIVATOR_DEFAULT_PRODUCT", "force_monitor") } lic := SignedLicense{ App: app, LicenseID: strings.TrimSpace(req.LicenseID), Customer: strings.TrimSpace(req.Customer), Fingerprint: normalizeFingerprint(req.Fingerprint), IssuedAt: strings.TrimSpace(req.IssuedAt), ExpiresAt: strings.TrimSpace(req.ExpiresAt), Features: parseFeatures(req.Features), } if lic.LicenseID == "" { lic.LicenseID = "LIC-" + time.Now().UTC().Format("20060102-150405") } if lic.Fingerprint == "" { return SignedLicense{}, "", "", errors.New("fingerprint is required") } if lic.IssuedAt == "" { lic.IssuedAt = time.Now().UTC().Format(time.RFC3339) } if _, err := time.Parse(time.RFC3339, lic.IssuedAt); err != nil { return SignedLicense{}, "", "", fmt.Errorf("issued_at must be RFC3339, example 2026-04-20T12:00:00Z") } if lic.ExpiresAt != "" { if _, err := time.Parse(time.RFC3339, lic.ExpiresAt); err != nil { return SignedLicense{}, "", "", fmt.Errorf("expires_at must be RFC3339, example 2027-04-20T00:00:00Z") } } privRaw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(privateKeyBase64)) if err != nil { return SignedLicense{}, "", "", fmt.Errorf("decode private key: %w", err) } if len(privRaw) != ed25519.PrivateKeySize { return SignedLicense{}, "", "", fmt.Errorf("invalid private key size: got %d want %d", len(privRaw), ed25519.PrivateKeySize) } priv := ed25519.PrivateKey(privRaw) payload := licensePayload{ App: lic.App, LicenseID: lic.LicenseID, Customer: lic.Customer, Fingerprint: lic.Fingerprint, IssuedAt: lic.IssuedAt, ExpiresAt: lic.ExpiresAt, Features: lic.Features, } payloadBytes, err := json.Marshal(payload) if err != nil { return SignedLicense{}, "", "", fmt.Errorf("marshal payload: %w", err) } sig := ed25519.Sign(priv, payloadBytes) lic.Signature = base64.StdEncoding.EncodeToString(sig) pub := priv.Public().(ed25519.PublicKey) return lic, string(payloadBytes), base64.StdEncoding.EncodeToString(pub), nil } func parseFeatures(raw string) []string { parts := strings.Split(raw, ",") out := make([]string, 0, len(parts)) seen := map[string]struct{}{} for _, p := range parts { v := strings.TrimSpace(p) if v == "" { continue } if _, ok := seen[v]; ok { continue } seen[v] = struct{}{} out = append(out, v) } return out } func normalizeFingerprint(v string) string { v = strings.TrimSpace(strings.ToUpper(v)) v = strings.ReplaceAll(v, "-", "") v = strings.ReplaceAll(v, " ", "") return v } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, err error) { writeJSON(w, status, map[string]string{"error": err.Error()}) } func allowMethod(w http.ResponseWriter, r *http.Request, method string) bool { if r.Method == http.MethodOptions { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.WriteHeader(http.StatusNoContent) return false } if r.Method != method { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return false } return true } func envOrDefault(key, def string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return def } func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() next.ServeHTTP(w, r) log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start).Round(time.Millisecond)) }) } const indexHTML = ` License Activator
License Activator v{{.Version}}

Offline License Generator

Create Ed25519 keypairs, parse activation requests from the machine, and generate signed license JSON for your protected Go app.

Important

Keep this activator private. Do not ship your private signing key with the customer machine. Only the public key belongs in the protected app config.

Env private key loaded: {{if .EnvPrivateKeyPresent}}yes{{else}}no{{end}}
Default product code: {{.DefaultProduct}}

1. Activation request from machine

Paste the JSON from /api/license/request of the protected machine, then parse it.

2. License data

3. Signing key

The generated public key must be copied into the protected app config as license.public_key_base64.

Generated keypair

Signed license JSON

Generate a license and copy the JSON into the protected machine activation page.
`