173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package client_test
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
|
|
"gitea.tecamino.local/paadi/licenseServer/client"
|
|
"gitea.tecamino.local/paadi/licenseServer/client/hwid"
|
|
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const testMachineID = "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344"
|
|
|
|
var testSecret = []byte("test-cache-secret-32bytes-minimum!")
|
|
|
|
func newTestServer(valid bool, message string) *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{"valid": valid, "message": message})
|
|
}))
|
|
}
|
|
|
|
func newClient(t *testing.T, serverURL string) *client.Client {
|
|
t.Helper()
|
|
c, err := client.New(client.Config{
|
|
ServerURL: serverURL,
|
|
LicenseKey: "TEST-1234-ABCD-5678",
|
|
AppName: "lictest_" + t.Name(),
|
|
GracePeriod: 7 * 24 * time.Hour,
|
|
CacheSecret: testSecret,
|
|
}, testMachineID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { os.Remove(c.CachePath()) })
|
|
return c
|
|
}
|
|
|
|
func signCache(key []byte, licenseKey, machineID, ts string) string {
|
|
payload := licenseKey + "|" + machineID + "|" + ts
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write([]byte(payload))
|
|
return hex.EncodeToString(mac.Sum(nil))
|
|
}
|
|
|
|
func writeCache(t *testing.T, path string, licenseKey, machineID string, validatedAt time.Time) {
|
|
t.Helper()
|
|
ts := validatedAt.UTC().Format(time.RFC3339)
|
|
entry := map[string]any{
|
|
"license_key": licenseKey,
|
|
"machine_id": machineID,
|
|
"last_validated": ts,
|
|
"sig": signCache(testSecret, licenseKey, machineID, ts),
|
|
}
|
|
os.MkdirAll(filepath.Dir(path), 0700)
|
|
data, _ := json.Marshal(entry)
|
|
os.WriteFile(path, data, 0600)
|
|
}
|
|
|
|
func TestOnlineValid(t *testing.T) {
|
|
srv := newTestServer(true, "")
|
|
defer srv.Close()
|
|
result := newClient(t, srv.URL).Check()
|
|
if !result.Valid || result.Offline {
|
|
t.Fatalf("expected online valid, got: %s", result.Message)
|
|
}
|
|
}
|
|
|
|
func TestOnlineInvalid(t *testing.T) {
|
|
srv := newTestServer(false, "license revoked")
|
|
defer srv.Close()
|
|
result := newClient(t, srv.URL).Check()
|
|
if result.Valid {
|
|
t.Fatal("expected invalid")
|
|
}
|
|
if result.Message != "license revoked" {
|
|
t.Fatalf("unexpected message: %s", result.Message)
|
|
}
|
|
}
|
|
|
|
func TestOnlineValidSavesCache(t *testing.T) {
|
|
srv := newTestServer(true, "")
|
|
defer srv.Close()
|
|
c := newClient(t, srv.URL)
|
|
c.Check()
|
|
if _, err := os.Stat(c.CachePath()); err != nil {
|
|
t.Fatal("expected cache file to be created after online validation")
|
|
}
|
|
}
|
|
|
|
func TestOfflineWithinGrace(t *testing.T) {
|
|
srv := newTestServer(true, "")
|
|
c := newClient(t, srv.URL)
|
|
c.Check()
|
|
srv.Close()
|
|
result := c.Check()
|
|
if !result.Valid || !result.Offline {
|
|
t.Fatalf("expected offline valid in grace period, got: %s", result.Message)
|
|
}
|
|
if result.GraceLeft <= 0 {
|
|
t.Fatal("expected positive grace remaining")
|
|
}
|
|
}
|
|
|
|
func TestOfflineExpiredGrace(t *testing.T) {
|
|
c := newClient(t, "http://127.0.0.1:19999")
|
|
writeCache(t, c.CachePath(), "TEST-1234-ABCD-5678", testMachineID, time.Now().Add(-8*24*time.Hour))
|
|
result := c.Check()
|
|
if result.Valid {
|
|
t.Fatal("expected invalid after grace period expired")
|
|
}
|
|
}
|
|
|
|
func TestOfflineNoServerNoCache(t *testing.T) {
|
|
result := newClient(t, "http://127.0.0.1:19999").Check()
|
|
if result.Valid {
|
|
t.Fatal("expected invalid with no server and no cache")
|
|
}
|
|
}
|
|
|
|
func TestOfflineWrongKey(t *testing.T) {
|
|
c := newClient(t, "http://127.0.0.1:19999")
|
|
writeCache(t, c.CachePath(), "DIFF-ERENT-KEY0-0000", testMachineID, time.Now())
|
|
result := c.Check()
|
|
if result.Valid {
|
|
t.Fatal("expected invalid when cached key does not match")
|
|
}
|
|
}
|
|
|
|
func TestOfflineWrongMachine(t *testing.T) {
|
|
c := newClient(t, "http://127.0.0.1:19999")
|
|
writeCache(t, c.CachePath(), "TEST-1234-ABCD-5678", "different-machine-id", time.Now())
|
|
result := c.Check()
|
|
if result.Valid {
|
|
t.Fatal("expected invalid when cached machine does not match")
|
|
}
|
|
}
|
|
|
|
func TestTamperedCache(t *testing.T) {
|
|
srv := newTestServer(true, "")
|
|
c := newClient(t, srv.URL)
|
|
c.Check()
|
|
srv.Close()
|
|
|
|
data, _ := os.ReadFile(c.CachePath())
|
|
var entry map[string]any
|
|
json.Unmarshal(data, &entry)
|
|
entry["last_validated"] = time.Now().Add(10 * time.Second).Format(time.RFC3339)
|
|
tampered, _ := json.Marshal(entry)
|
|
os.WriteFile(c.CachePath(), tampered, 0600)
|
|
|
|
result := c.Check()
|
|
if result.Valid {
|
|
t.Fatal("tampered cache should be rejected")
|
|
}
|
|
}
|
|
|
|
func TestGetMachineHash(t *testing.T) {
|
|
fp, err := hwid.Collect()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Log(fp.Hash())
|
|
}
|