Pulse/pkg/licensing/public_key.go
2026-03-18 16:06:30 +00:00

115 lines
3.4 KiB
Go

package licensing
import (
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"errors"
"os"
"strings"
"github.com/rs/zerolog/log"
)
var ErrMalformedPublicKey = errors.New("malformed license public key")
// EmbeddedPublicKey is the production Ed25519 public key (base64 encoded).
// This is typically set at build time via -ldflags:
//
// -X github.com/rcourtman/pulse-go-rewrite/pkg/licensing.EmbeddedPublicKey=BASE64_KEY
var EmbeddedPublicKey string
// PublicKeyFingerprint returns an SHA256 fingerprint for logging.
func PublicKeyFingerprint(key ed25519.PublicKey) string {
if len(key) == 0 {
return ""
}
sum := sha256.Sum256(key)
return "SHA256:" + base64.StdEncoding.EncodeToString(sum[:])
}
// InitEmbeddedPublicKey initializes the runtime license public key using
// EmbeddedPublicKey and standard environment precedence rules.
func InitEmbeddedPublicKey() {
InitPublicKey(EmbeddedPublicKey, isLicenseValidationDevMode(), SetPublicKey)
}
// InitPublicKey initializes the runtime license public key.
//
// In release builds (allowEnvOverride=false):
//
// Only the compile-time embedded key is used. The PULSE_LICENSE_PUBLIC_KEY
// env var is ignored to prevent users from substituting their own key and
// self-signing license JWTs.
//
// In dev builds (allowEnvOverride=true):
// 1. PULSE_LICENSE_PUBLIC_KEY environment variable (base64 encoded)
// 2. embeddedPublicKey parameter (set at compile time via -ldflags)
// 3. If devMode=true, skip validation (development only)
func InitPublicKey(embeddedPublicKey string, devMode bool, setPublicKey func(ed25519.PublicKey)) {
if setPublicKey == nil {
log.Error().Msg("license public key init skipped: setPublicKey callback is nil")
return
}
// Environment override is only allowed in non-release builds.
if allowPublicKeyEnvOverride() {
if envKey := os.Getenv("PULSE_LICENSE_PUBLIC_KEY"); envKey != "" {
key, err := DecodePublicKey(envKey)
if err != nil {
log.Error().Err(err).Msg("failed to decode PULSE_LICENSE_PUBLIC_KEY, trying embedded key")
// Fall through to try embedded key instead of returning
} else {
setPublicKey(key)
log.Info().
Str("source", "environment").
Str("fingerprint", PublicKeyFingerprint(key)).
Msg("license public key loaded")
return
}
}
}
// Embedded key (set at compile time via -ldflags)
if embeddedPublicKey != "" {
key, err := DecodePublicKey(embeddedPublicKey)
if err != nil {
log.Error().Err(err).Msg("failed to decode embedded public key")
} else {
setPublicKey(key)
log.Info().
Str("source", "embedded").
Str("fingerprint", PublicKeyFingerprint(key)).
Msg("license public key loaded")
return
}
}
// No key available
if devMode {
log.Warn().Msg("License validation running in DEV MODE - signatures not verified")
} else {
log.Warn().Msg("no license public key configured - license activation will fail")
}
}
// DecodePublicKey decodes a base64-encoded Ed25519 public key.
func DecodePublicKey(encoded string) (ed25519.PublicKey, error) {
// Remove any whitespace
encoded = strings.TrimSpace(encoded)
// Try standard base64 first, then URL-safe
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
decoded, err = base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return nil, err
}
}
if len(decoded) != ed25519.PublicKeySize {
return nil, ErrMalformedPublicKey
}
return ed25519.PublicKey(decoded), nil
}