internal/license: Ed25519-signierte Lizenzschluessel (stdlib crypto/ed25519, keine neue Abhaengigkeit). Issuer haelt den privaten Schluessel (lebt beim Lizenzgeber), Validator nur den oeffentlichen (lebt im Core-Prozess) — klare Trennung Ausstellung/Pruefung nach Unleash-Vorbild (Flag-Verwaltung vs. Flag-Auswertung). Store.Install prueft NUR die Signatur und persistiert den Lizenzumfang (Plan, Modul-Liste, Laufzeit) in tenant_licenses (Registry-DB, 1:1 zu tenants). Eine bereits abgelaufene, aber korrekt signierte Lizenz laesst sich trotzdem einspielen — der Ablauf wird erst bei Store.RequireActive bewertet (liefert ErrLicenseExpired statt Panic/Absturz), waehrend Store.Status den Umfang unabhaengig vom Ablauf weiterhin liefert. Neu: scripts/run-checks.sh buendelt reset-test-env.sh + go build/vet/test (-p 1) zu einem Ein-Kommando-Check fuer den Testhost. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Manipulierter Lizenzschluessel zuverlaessig erkannt — TestParse_RejectsTamperedKey, TestParse_RejectsWrongKeyPair, TestStore_InstallRejectsInvalidSignature. PASS. 2. Ablauf loest definierten eingeschraenkten Zustand aus, kein harter Systemausfall — TestStore_RequireActive_DetectsExpiry (inkl. recover()- Absicherung im Test, dass kein Panic auftritt), ErrLicenseExpired statt Absturz; Status bleibt trotzdem abfragbar. PASS. 3. Signaturpruefung von zweiter Person gegen Dokumentation nachvollzogen — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
107 lines
2.6 KiB
Go
107 lines
2.6 KiB
Go
package license
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func testKeyPair(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
|
t.Helper()
|
|
pub, priv, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
t.Fatalf("schluesselpaar erzeugen: %v", err)
|
|
}
|
|
return pub, priv
|
|
}
|
|
|
|
func TestIssueAndParse_RoundTrip(t *testing.T) {
|
|
pub, priv := testKeyPair(t)
|
|
issuer := NewIssuer(priv)
|
|
validator := NewValidator(pub)
|
|
|
|
payload := Payload{
|
|
TenantSlug: "acme",
|
|
Plan: "pro",
|
|
Modules: []string{"dms", "mail"},
|
|
IssuedAt: time.Now().Truncate(time.Second),
|
|
ValidUntil: time.Now().Add(365 * 24 * time.Hour).Truncate(time.Second),
|
|
}
|
|
|
|
key, err := issuer.Issue(payload)
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
|
|
got, err := validator.Parse(key)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if got.TenantSlug != payload.TenantSlug || got.Plan != payload.Plan || len(got.Modules) != 2 {
|
|
t.Fatalf("payload nach parse unerwartet: %+v", got)
|
|
}
|
|
}
|
|
|
|
// Akzeptanzkriterium 1 + Pruefung 1: manipulierter Schluessel wird zuverlaessig erkannt.
|
|
func TestParse_RejectsTamperedKey(t *testing.T) {
|
|
pub, priv := testKeyPair(t)
|
|
issuer := NewIssuer(priv)
|
|
validator := NewValidator(pub)
|
|
|
|
key, err := issuer.Issue(Payload{TenantSlug: "acme", Plan: "pro", ValidUntil: time.Now().Add(time.Hour)})
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
|
|
// Ein Zeichen im signierten Teil aendern.
|
|
tampered := []byte(key)
|
|
changed := false
|
|
for i, c := range tampered {
|
|
if c != '.' {
|
|
if c == 'A' {
|
|
tampered[i] = 'B'
|
|
} else {
|
|
tampered[i] = 'A'
|
|
}
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
if !changed {
|
|
t.Fatal("testaufbau fehlerhaft: nichts zum manipulieren gefunden")
|
|
}
|
|
|
|
if _, err := validator.Parse(string(tampered)); !errors.Is(err, ErrInvalidSignature) {
|
|
t.Fatalf("erwartet ErrInvalidSignature, habe %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParse_RejectsWrongKeyPair(t *testing.T) {
|
|
_, priv := testKeyPair(t)
|
|
otherPub, _ := testKeyPair(t)
|
|
|
|
issuer := NewIssuer(priv)
|
|
validator := NewValidator(otherPub) // falscher oeffentlicher Schluessel
|
|
|
|
key, err := issuer.Issue(Payload{TenantSlug: "acme", Plan: "pro"})
|
|
if err != nil {
|
|
t.Fatalf("issue: %v", err)
|
|
}
|
|
if _, err := validator.Parse(key); !errors.Is(err, ErrInvalidSignature) {
|
|
t.Fatalf("erwartet ErrInvalidSignature, habe %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParse_RejectsMalformedKey(t *testing.T) {
|
|
pub, _ := testKeyPair(t)
|
|
validator := NewValidator(pub)
|
|
|
|
cases := []string{"", "keine-punkt-trennung", "!!!.!!!"}
|
|
for _, c := range cases {
|
|
if _, err := validator.Parse(c); !errors.Is(err, ErrMalformedKey) {
|
|
t.Fatalf("Parse(%q): erwartet ErrMalformedKey, habe %v", c, err)
|
|
}
|
|
}
|
|
}
|