feat(license): replace forgeable rotor cipher with Ed25519-signed tokens - #409
Merged
Conversation
The 16-char rotor-cipher + self-checksum scheme shipped its own generator (GenerateLicenseKey) inside every Stem binary, so any copy could mint valid Professional keys. Replace it with offline-verifiable Ed25519-signed tokens (signing.go), harmonized with the niac/seed format and the keygen tool: MSN1.<base64url(payload)>.<base64url(signature)> The binary embeds only the Ed25519 public key; the private key lives solely in the keygen tool. Verifier.Validate checks the signature before interpreting any field, then enforces scheme/version/product==stem/tier/code-pairing/expiry. Tier->feature mapping stays in-binary; deprecated Enterprise (3001/tier3) tokens keep validating and grant Pro features. The TierProfessional/TierTestSuite alias and 1001/2001/3001 wire codes are preserved. - Delete cipher.go + GenerateLicenseKey (the forgeability vector). - Manager gains a verifier field + NewManagerWithVerifier so tests activate tokens minted with an ephemeral key without the production private key. - Tests rewired to the signed format: rotor/checksum/alphanumeric tests removed (tested deleted code); GenerateLicenseKey->signTestKey, ValidateLicenseKey-> testVerifier, NewManager->NewManagerWithVerifier; forge/tamper rejection and per-tier production-signed contract vectors pin the cross-tool contract. - FormatKey no longer strips characters (base64url uses '-'/'_'). - ADR-0007 records the decision; one narrow unparam exclusion documents the signTestKey (string,error) drop-in shape. Pre-launch: no issued keys to honor. Embedded key is a pre-launch key; rotate via keygen before GA. Verified: go build ./..., go vet ./..., golangci-lint (0 issues, license + consumers), go test -race ./internal/license/ (ok 101s) + ./internal/api/ (ok 512s).
krisarmstrong
enabled auto-merge (squash)
June 8, 2026 13:30
Contributor
License Compliance ReportAll dependencies pass license compliance checksGo Dependencies
npm DependenciesSee full report in workflow artifacts Allowed Licenses: MIT, Apache-2.0, BSD-*, ISC, CC0-1.0, MPL-2.0 |
This was referenced Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces Stem's forgeable license scheme with offline-verifiable Ed25519-signed tokens, harmonized with niac (#802) and the keygen tool. The old 16-char rotor-cipher + self-checksum shipped its own generator (
GenerateLicenseKey) inside every binary — any copy could mint valid Professional keys. This is the Ed25519 license item from the stem/niac remediation plan (ADR-0007).Token format (
signing.go){v, product, code, serial, tier, maxDevices, iat, exp}What changed
Verifier.Validatechecks the signature before interpreting any field, then enforces scheme/version/product==stem/tier/code-pairing (1001→Reflector, 2001→Professional, 3001→Enterprise)/expiry. Tier→feature mapping stays in-binary; deprecated Enterprise tokens keep validating and grant Pro features.TierProfessional/TierTestSuitealias and wire codes preserved.cipher.go+GenerateLicenseKey(the forgeability vector).Managergains averifierfield +NewManagerWithVerifierso tests activate ephemeral-key tokens without the production private key.GenerateLicenseKey→signTestKey,ValidateLicenseKey→testVerifier,NewManager→NewManagerWithVerifier. Added forge/tamper rejection + per-tier production-signed contract vectors.FormatKeyno longer strips characters (base64url uses-/_).Security property
TestForgeryRejected/TestForgeryRejectedInternalmint an attacker-key token and assert the production verifier rejects it withErrLicenseInvalid. Validation stays fully offline (no phone-home).Verification
go build ./...,go vet ./...cleangolangci-lint— 0 issues (license + consumers); one narrow documentedunparamexclusion for thesignTestKey(string,error) drop-in shapego test -race ./internal/license/(ok 101s) +./internal/api/(ok 512s)