81a621423a
Pure-Go, stdlib-only implementation of the LIC1 wire format: - ParseKey + Verify + ParseAndVerify for offline verification - HashFingerprint helper (SHA-256, matching the daemon's contract) - LoadPublicKeyPEM for the standard PKIX-encoded Ed25519 public keys the daemon emits - Client.Validate / Client.PublicKey for online checks against a running Keysat daemon - LicensePayload struct with idiomatic Go getters (IsTrial, IsFingerprintBound, IsExpiredAt, HasEntitlement) Wire-format crosscheck against the shared tests/crosscheck/vector.json (the same file the Rust, TypeScript, Python SDKs and the daemon itself test against). All four fixtures pass — v1 legacy fingerprint-bound, v2 trial with entitlements, v2 perpetual unbound, plus end-to-end PEM-load → ParseAndVerify signature roundtrip. Confirms byte-for-byte agreement across five independent implementations. No third-party dependencies. Module path: github.com/keysat-xyz/keysat-client-go go 1.21
181 lines
6.0 KiB
Go
181 lines
6.0 KiB
Go
// Wire-format crosscheck. Each fixture in the shared
|
|
// tests/crosscheck/vector.json was generated by Python's
|
|
// reference_signer.py (independent crypto), then is consumed by
|
|
// every Keysat SDK + the daemon. If this test fails, this Go SDK
|
|
// has drifted from the wire format the rest of the ecosystem
|
|
// agrees on.
|
|
//
|
|
// The path to vector.json assumes this package is checked out
|
|
// under the parent licensing/ workspace (next to
|
|
// licensing-client-rust, etc.). Skip the test gracefully if the
|
|
// vector isn't reachable — when this SDK is fetched standalone
|
|
// via `go get`, there's nothing to cross-check against.
|
|
|
|
package keysat_test
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/keysat-xyz/keysat-client-go"
|
|
)
|
|
|
|
type fixture struct {
|
|
LicenseKey string `json:"licenseKey"`
|
|
Expected struct {
|
|
Version int `json:"version"`
|
|
ProductUUID string `json:"productUuid"`
|
|
LicenseUUID string `json:"licenseUuid"`
|
|
IssuedAt int64 `json:"issuedAt"`
|
|
ExpiresAt int64 `json:"expiresAt"`
|
|
Flags int `json:"flags"`
|
|
IsFingerprintBound bool `json:"isFingerprintBound"`
|
|
IsTrial bool `json:"isTrial"`
|
|
Entitlements []string `json:"entitlements"`
|
|
FingerprintRaw *string `json:"fingerprintRaw"`
|
|
FingerprintHashHex string `json:"fingerprintHashHex"`
|
|
} `json:"expected"`
|
|
}
|
|
|
|
type vectorFile struct {
|
|
PublicKeyPEM string `json:"publicKeyPem"`
|
|
V1 fixture `json:"v1"`
|
|
V2 fixture `json:"v2"`
|
|
V2PerpetualUnbound fixture `json:"v2_perpetual_unbound"`
|
|
}
|
|
|
|
func loadVector(t *testing.T) vectorFile {
|
|
t.Helper()
|
|
candidates := []string{
|
|
"../tests/crosscheck/vector.json", // when this is a sibling of /tests
|
|
"../../tests/crosscheck/vector.json", // when nested one deeper
|
|
}
|
|
for _, c := range candidates {
|
|
abs, err := filepath.Abs(c)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
raw, err := os.ReadFile(abs)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("reading %s: %v", abs, err)
|
|
}
|
|
var v vectorFile
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
t.Fatalf("parsing %s: %v", abs, err)
|
|
}
|
|
return v
|
|
}
|
|
t.Skipf("crosscheck vector.json not found alongside this package " +
|
|
"(expected at ../tests/crosscheck/vector.json); skipping. " +
|
|
"This is normal when fetched standalone via `go get` — the " +
|
|
"crosscheck only runs from the parent licensing/ workspace.")
|
|
return vectorFile{}
|
|
}
|
|
|
|
func uuidString(b [16]byte) string {
|
|
// Render as canonical 8-4-4-4-12 lowercase hex.
|
|
hexStr := hex.EncodeToString(b[:])
|
|
return hexStr[0:8] + "-" + hexStr[8:12] + "-" + hexStr[12:16] + "-" +
|
|
hexStr[16:20] + "-" + hexStr[20:32]
|
|
}
|
|
|
|
func checkFixture(t *testing.T, name string, fx fixture) {
|
|
t.Helper()
|
|
payload, _, _, err := keysat.ParseKey(fx.LicenseKey)
|
|
if err != nil {
|
|
t.Fatalf("%s: ParseKey failed: %v", name, err)
|
|
}
|
|
if int(payload.Version) != fx.Expected.Version {
|
|
t.Errorf("%s: version = %d, want %d", name, payload.Version, fx.Expected.Version)
|
|
}
|
|
if uuidString(payload.ProductID) != fx.Expected.ProductUUID {
|
|
t.Errorf("%s: productID = %s, want %s", name, uuidString(payload.ProductID), fx.Expected.ProductUUID)
|
|
}
|
|
if uuidString(payload.LicenseID) != fx.Expected.LicenseUUID {
|
|
t.Errorf("%s: licenseID = %s, want %s", name, uuidString(payload.LicenseID), fx.Expected.LicenseUUID)
|
|
}
|
|
if payload.IssuedAt != fx.Expected.IssuedAt {
|
|
t.Errorf("%s: issuedAt = %d, want %d", name, payload.IssuedAt, fx.Expected.IssuedAt)
|
|
}
|
|
if payload.ExpiresAt != fx.Expected.ExpiresAt {
|
|
t.Errorf("%s: expiresAt = %d, want %d", name, payload.ExpiresAt, fx.Expected.ExpiresAt)
|
|
}
|
|
if int(payload.Flags) != fx.Expected.Flags {
|
|
t.Errorf("%s: flags = %d, want %d", name, payload.Flags, fx.Expected.Flags)
|
|
}
|
|
if payload.IsFingerprintBound() != fx.Expected.IsFingerprintBound {
|
|
t.Errorf("%s: IsFingerprintBound = %v, want %v", name, payload.IsFingerprintBound(), fx.Expected.IsFingerprintBound)
|
|
}
|
|
if payload.IsTrial() != fx.Expected.IsTrial {
|
|
t.Errorf("%s: IsTrial = %v, want %v", name, payload.IsTrial(), fx.Expected.IsTrial)
|
|
}
|
|
got := payload.Entitlements
|
|
if got == nil {
|
|
got = []string{}
|
|
}
|
|
want := fx.Expected.Entitlements
|
|
if want == nil {
|
|
want = []string{}
|
|
}
|
|
if len(got) != len(want) {
|
|
t.Errorf("%s: entitlements len = %d, want %d", name, len(got), len(want))
|
|
} else {
|
|
for i := range got {
|
|
if got[i] != want[i] {
|
|
t.Errorf("%s: entitlements[%d] = %q, want %q", name, i, got[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
gotFP := hex.EncodeToString(payload.FingerprintHash[:])
|
|
if gotFP != fx.Expected.FingerprintHashHex {
|
|
t.Errorf("%s: fingerprintHash = %s, want %s", name, gotFP, fx.Expected.FingerprintHashHex)
|
|
}
|
|
// If a raw fingerprint is supplied, verify HashFingerprint reproduces the wire bytes.
|
|
if fx.Expected.FingerprintRaw != nil && fx.Expected.IsFingerprintBound {
|
|
h := keysat.HashFingerprint(*fx.Expected.FingerprintRaw)
|
|
if hex.EncodeToString(h[:]) != fx.Expected.FingerprintHashHex {
|
|
t.Errorf("%s: HashFingerprint(raw) does not match wire hash", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCrosscheck_V1(t *testing.T) {
|
|
v := loadVector(t)
|
|
checkFixture(t, "v1", v.V1)
|
|
}
|
|
|
|
func TestCrosscheck_V2_TrialWithEntitlements(t *testing.T) {
|
|
v := loadVector(t)
|
|
checkFixture(t, "v2", v.V2)
|
|
}
|
|
|
|
func TestCrosscheck_V2_PerpetualUnbound(t *testing.T) {
|
|
v := loadVector(t)
|
|
checkFixture(t, "v2_perpetual_unbound", v.V2PerpetualUnbound)
|
|
}
|
|
|
|
// Exercise signature verification end-to-end: load the vector's
|
|
// public key, parse the v2 fixture, verify. Locks in the
|
|
// PEM → ed25519.PublicKey path on top of the parser.
|
|
func TestCrosscheck_V2_SignatureVerifies(t *testing.T) {
|
|
v := loadVector(t)
|
|
pub, err := keysat.LoadPublicKeyPEM(v.PublicKeyPEM)
|
|
if err != nil {
|
|
t.Fatalf("LoadPublicKeyPEM: %v", err)
|
|
}
|
|
payload, err := keysat.ParseAndVerify(v.V2.LicenseKey, pub)
|
|
if err != nil {
|
|
t.Fatalf("ParseAndVerify: %v", err)
|
|
}
|
|
if payload.Version != keysat.KeyVersionV2 {
|
|
t.Errorf("unexpected version: %d", payload.Version)
|
|
}
|
|
}
|