Initial public commit
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Cross-check the TypeScript SDK against the canonical wire-format
|
||||
* test vectors at ./vector.json (mirrored from
|
||||
* <repo>/tests/crosscheck/vector.json).
|
||||
*
|
||||
* These are the same vectors exercised by the Python and Rust SDKs.
|
||||
* Any new SDK must pass every fixture in vector.json — that's how we
|
||||
* guarantee wire-format compatibility across languages.
|
||||
*
|
||||
* Run with: `npm test`.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
Verifier,
|
||||
PublicKey,
|
||||
parseLicenseKey,
|
||||
isExpiredAt,
|
||||
hasEntitlement,
|
||||
hashFingerprint,
|
||||
LicensingError,
|
||||
KEY_VERSION_V1,
|
||||
KEY_VERSION_V2,
|
||||
} from '../src/index.js'
|
||||
|
||||
interface Fixture {
|
||||
licenseKey: string
|
||||
expected: {
|
||||
version: number
|
||||
productUuid: string
|
||||
licenseUuid: string
|
||||
issuedAt: number
|
||||
expiresAt: number
|
||||
flags: number
|
||||
isFingerprintBound: boolean
|
||||
isTrial: boolean
|
||||
entitlements: string[]
|
||||
fingerprintRaw: string | null
|
||||
fingerprintHashHex: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Vectors {
|
||||
publicKeyPem: string
|
||||
v1: Fixture
|
||||
v2: Fixture
|
||||
v2_perpetual_unbound?: Fixture
|
||||
}
|
||||
|
||||
const vectors: Vectors = JSON.parse(
|
||||
readFileSync(join(__dirname, 'vector.json'), 'utf8'),
|
||||
)
|
||||
|
||||
const verifier = new Verifier(PublicKey.fromPem(vectors.publicKeyPem))
|
||||
|
||||
function bytesToHex(b: Uint8Array): string {
|
||||
return Array.from(b)
|
||||
.map((x) => x.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// v1 fixture: legacy fixed-74 layout, fingerprint-bound, no expiry,
|
||||
// no entitlements.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('v1 fixture', () => {
|
||||
it('parses', () => {
|
||||
const parsed = parseLicenseKey(vectors.v1.licenseKey)
|
||||
const exp = vectors.v1.expected
|
||||
expect(parsed.payload.version).toBe(exp.version)
|
||||
expect(parsed.payload.version).toBe(KEY_VERSION_V1)
|
||||
expect(parsed.payload.productUuid).toBe(exp.productUuid)
|
||||
expect(parsed.payload.licenseUuid).toBe(exp.licenseUuid)
|
||||
expect(parsed.payload.issuedAt).toBe(exp.issuedAt)
|
||||
expect(parsed.payload.expiresAt).toBe(exp.expiresAt)
|
||||
expect(parsed.payload.flags).toBe(exp.flags)
|
||||
expect(parsed.payload.isFingerprintBound).toBe(exp.isFingerprintBound)
|
||||
expect(parsed.payload.isTrial).toBe(exp.isTrial)
|
||||
expect(parsed.payload.entitlements).toEqual(exp.entitlements)
|
||||
expect(bytesToHex(parsed.payload.fingerprintHash)).toBe(
|
||||
exp.fingerprintHashHex,
|
||||
)
|
||||
})
|
||||
|
||||
it('verifies signature', () => {
|
||||
const ok = verifier.verify(vectors.v1.licenseKey)
|
||||
expect(ok.productId).toBe(vectors.v1.expected.productUuid)
|
||||
expect(ok.licenseId).toBe(vectors.v1.expected.licenseUuid)
|
||||
})
|
||||
|
||||
it('detects tampering', () => {
|
||||
const key = vectors.v1.licenseKey
|
||||
const dashIdx = key.indexOf('-')
|
||||
expect(dashIdx).toBeGreaterThan(0)
|
||||
const payloadStart = dashIdx + 1
|
||||
const swapChar = key[payloadStart] !== 'B' ? 'B' : 'C'
|
||||
const tampered =
|
||||
key.substring(0, payloadStart) +
|
||||
swapChar +
|
||||
key.substring(payloadStart + 1)
|
||||
expect(() => verifier.verify(tampered)).toThrow(LicensingError)
|
||||
})
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// v2 fixture: trial, fingerprint-bound, explicit expiry, two entitlements.
|
||||
// Stresses the variable-length tail parser.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('v2 fixture', () => {
|
||||
it('parses', () => {
|
||||
const parsed = parseLicenseKey(vectors.v2.licenseKey)
|
||||
const exp = vectors.v2.expected
|
||||
expect(parsed.payload.version).toBe(exp.version)
|
||||
expect(parsed.payload.version).toBe(KEY_VERSION_V2)
|
||||
expect(parsed.payload.productUuid).toBe(exp.productUuid)
|
||||
expect(parsed.payload.licenseUuid).toBe(exp.licenseUuid)
|
||||
expect(parsed.payload.issuedAt).toBe(exp.issuedAt)
|
||||
expect(parsed.payload.expiresAt).toBe(exp.expiresAt)
|
||||
expect(parsed.payload.flags).toBe(exp.flags)
|
||||
expect(parsed.payload.isFingerprintBound).toBe(exp.isFingerprintBound)
|
||||
expect(parsed.payload.isTrial).toBe(exp.isTrial)
|
||||
expect(parsed.payload.entitlements).toEqual(exp.entitlements)
|
||||
})
|
||||
|
||||
it('verifies signature', () => {
|
||||
const ok = verifier.verify(vectors.v2.licenseKey)
|
||||
expect(ok.productId).toBe(vectors.v2.expected.productUuid)
|
||||
})
|
||||
|
||||
it('expiry boundary is exact', () => {
|
||||
const parsed = parseLicenseKey(vectors.v2.licenseKey)
|
||||
const expiresAt = parsed.payload.expiresAt
|
||||
expect(isExpiredAt(parsed.payload, expiresAt)).toBe(true)
|
||||
expect(isExpiredAt(parsed.payload, expiresAt - 1)).toBe(false)
|
||||
})
|
||||
|
||||
it('reports configured entitlements', () => {
|
||||
const parsed = parseLicenseKey(vectors.v2.licenseKey)
|
||||
for (const slug of vectors.v2.expected.entitlements) {
|
||||
expect(hasEntitlement(parsed.payload, slug)).toBe(true)
|
||||
}
|
||||
expect(
|
||||
hasEntitlement(parsed.payload, 'definitely-not-a-real-entitlement'),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// v2_perpetual_unbound — common case for paid purchase: v2, no expiry,
|
||||
// no fingerprint binding, no entitlements.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('v2_perpetual_unbound fixture', () => {
|
||||
it('parses if present', () => {
|
||||
if (!vectors.v2_perpetual_unbound) return
|
||||
const parsed = parseLicenseKey(vectors.v2_perpetual_unbound.licenseKey)
|
||||
expect(parsed.payload.version).toBe(2)
|
||||
expect(parsed.payload.expiresAt).toBe(0)
|
||||
expect(parsed.payload.isFingerprintBound).toBe(false)
|
||||
})
|
||||
|
||||
it('verifies if present', () => {
|
||||
if (!vectors.v2_perpetual_unbound) return
|
||||
const ok = verifier.verify(vectors.v2_perpetual_unbound.licenseKey)
|
||||
expect(ok.productId).toBe(
|
||||
vectors.v2_perpetual_unbound.expected.productUuid,
|
||||
)
|
||||
})
|
||||
|
||||
it('never reports expired (perpetual)', () => {
|
||||
if (!vectors.v2_perpetual_unbound) return
|
||||
const parsed = parseLicenseKey(vectors.v2_perpetual_unbound.licenseKey)
|
||||
expect(isExpiredAt(parsed.payload, 9_999_999_999)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Cross-language fingerprint-hash compatibility.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('hashFingerprint', () => {
|
||||
it('matches Python stdlib SHA-256 hex output', () => {
|
||||
const hashed = bytesToHex(hashFingerprint('hello'))
|
||||
// Python: hashlib.sha256(b"hello").hexdigest()
|
||||
expect(hashed).toBe(
|
||||
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
)
|
||||
})
|
||||
|
||||
it('matches the v1 fixture fingerprint hash', () => {
|
||||
const raw = vectors.v1.expected.fingerprintRaw
|
||||
if (raw == null) return
|
||||
const hashed = bytesToHex(hashFingerprint(raw))
|
||||
expect(hashed).toBe(vectors.v1.expected.fingerprintHashHex)
|
||||
})
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Negative cases.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
describe('negative cases', () => {
|
||||
it('rejects a too-short key', () => {
|
||||
expect(() => parseLicenseKey('notakey')).toThrow(LicensingError)
|
||||
})
|
||||
|
||||
it('rejects a wrong prefix', () => {
|
||||
expect(() => parseLicenseKey('LIC9-AAAA-BBBB')).toThrow(LicensingError)
|
||||
})
|
||||
|
||||
it('rejects an empty string', () => {
|
||||
expect(() => parseLicenseKey('')).toThrow(LicensingError)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user