Files
zeekay f77da86cda Initial luxfi/license — Ed25519-signed offline license tokens for commercial Lux features.
- Stdlib-only deterministic binary token format (no JWT / CBOR dep).
- Go API: VerifyToken, LoadFromEnv, HasFeature, HasFeatureOrDie.
- Stable C ABI + C++17 RAII wrapper for non-Go commercial binaries (luxcpp/cevm).
- cmd/lux-license: keygen / issue / inspect / verify.
- Embedded development key; warns once at runtime until production key swap.
- 22 Go tests + 1 cgo round-trip test, all passing.
2026-05-15 17:08:32 -07:00

132 lines
4.2 KiB
Go

// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package license
import (
"crypto/ed25519"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
)
// tokenMagic is a 4-byte prefix used to detect mangled or wrong-format input
// before attempting signature verification. "LUXL" = Lux License.
var tokenMagic = [4]byte{'L', 'U', 'X', 'L'}
// tokenVersion identifies the framing layout.
//
// magic[4] = "LUXL"
// version uint8 = 1
// claims_len uint32 (big-endian)
// claims_bytes [claims_len]
// signature[64]
const tokenVersion uint8 = 1
// Errors returned by Verify / Parse.
var (
ErrBadMagic = errors.New("license: bad token magic")
ErrBadVersion = errors.New("license: unsupported token version")
ErrTruncated = errors.New("license: truncated token")
ErrBadSig = errors.New("license: signature verification failed")
ErrExpired = errors.New("license: token expired")
)
// Sign produces a fully framed, signed token from claims using priv.
//
// The Lux production signing key is held offline / in KMS. This function is
// public so customers and internal automation can mint test tokens; the
// runtime APIs (VerifyToken, LoadFromEnv, HasFeature, HasFeatureOrDie) never
// touch a private key.
func Sign(claims *Claims, priv ed25519.PrivateKey) ([]byte, error) {
if len(priv) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("license: priv key size %d != %d", len(priv), ed25519.PrivateKeySize)
}
encoded, err := claims.Encode()
if err != nil {
return nil, err
}
sig := ed25519.Sign(priv, encoded)
out := make([]byte, 0, 4+1+4+len(encoded)+ed25519.SignatureSize)
out = append(out, tokenMagic[:]...)
out = append(out, tokenVersion)
out = binary.BigEndian.AppendUint32(out, uint32(len(encoded)))
out = append(out, encoded...)
out = append(out, sig...)
return out, nil
}
// ParseToken splits a binary token into its claims payload and signature,
// without verifying the signature. Use VerifyToken for the verifying form.
func ParseToken(tok []byte) (claimsPayload, signature []byte, err error) {
if len(tok) < 4+1+4+ed25519.SignatureSize {
return nil, nil, ErrTruncated
}
if tok[0] != tokenMagic[0] || tok[1] != tokenMagic[1] || tok[2] != tokenMagic[2] || tok[3] != tokenMagic[3] {
return nil, nil, ErrBadMagic
}
if tok[4] != tokenVersion {
return nil, nil, ErrBadVersion
}
clen := binary.BigEndian.Uint32(tok[5:9])
if uint64(9)+uint64(clen)+uint64(ed25519.SignatureSize) != uint64(len(tok)) {
return nil, nil, ErrTruncated
}
claimsPayload = tok[9 : 9+clen]
signature = tok[9+clen:]
return claimsPayload, signature, nil
}
// VerifyToken parses tok, verifies its signature against IssuerPublicKey, and
// checks that the embedded expiry has not passed (using time.Now). On success
// it returns the parsed Claims.
func VerifyToken(tok []byte) (*Claims, error) {
warnIfDevKey()
return verifyTokenAt(tok, IssuerPublicKey, time.Now())
}
// VerifyTokenWithKey is the same as VerifyToken but allows an explicit public
// key. Used by tests and by anyone needing to verify tokens against a
// non-production signer (e.g. a per-tenant issuer).
func VerifyTokenWithKey(tok []byte, pub ed25519.PublicKey) (*Claims, error) {
return verifyTokenAt(tok, pub, time.Now())
}
func verifyTokenAt(tok []byte, pub ed25519.PublicKey, now time.Time) (*Claims, error) {
if len(pub) != ed25519.PublicKeySize {
return nil, fmt.Errorf("license: pub key size %d != %d", len(pub), ed25519.PublicKeySize)
}
payload, sig, err := ParseToken(tok)
if err != nil {
return nil, err
}
if !ed25519.Verify(pub, payload, sig) {
return nil, ErrBadSig
}
c, err := DecodeClaims(payload)
if err != nil {
return nil, err
}
if c.Expired(now) {
return c, ErrExpired
}
return c, nil
}
// EncodeBase64 returns the standard-base64 encoding of a binary token, useful
// for transporting tokens as environment variables.
func EncodeBase64(tok []byte) string {
return base64.StdEncoding.EncodeToString(tok)
}
// DecodeBase64 reverses EncodeBase64. Leading and trailing whitespace is
// tolerated so file-based tokens round-trip cleanly.
func DecodeBase64(s string) ([]byte, error) {
s = strings.TrimSpace(s)
return base64.StdEncoding.DecodeString(s)
}