843ff0e5d7
Glue files not covered by subproject repos: top-level docs, logo, keysat-design-system, and crosscheck tests. Subproject folders are gitignored (each has its own Gitea remote).
139 lines
4.7 KiB
JavaScript
139 lines
4.7 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import {
|
|
Verifier,
|
|
PublicKey,
|
|
hashFingerprint,
|
|
parseLicenseKey,
|
|
isExpiredAt,
|
|
hasEntitlement,
|
|
} from '/sessions/hopeful-determined-edison/ts-sdk-build/dist/index.js'
|
|
|
|
const vector = JSON.parse(readFileSync(new URL('./vector.json', import.meta.url), 'utf8'))
|
|
|
|
let failures = 0
|
|
function check(name, cond, extra = '') {
|
|
if (cond) {
|
|
console.log(` OK ${name}`)
|
|
} else {
|
|
console.log(` FAIL ${name}${extra ? ': ' + extra : ''}`)
|
|
failures++
|
|
}
|
|
}
|
|
|
|
const toHex = (b) =>
|
|
Array.from(b).map((x) => x.toString(16).padStart(2, '0')).join('')
|
|
|
|
const verifier = new Verifier(PublicKey.fromPem(vector.publicKeyPem))
|
|
|
|
function runCase(label, caseData) {
|
|
console.log(`\n== ${label}: parseLicenseKey + verify ==`)
|
|
const expected = caseData.expected
|
|
const parsed = parseLicenseKey(caseData.licenseKey)
|
|
|
|
check(`${label}.version`, parsed.payload.version === expected.version,
|
|
`got ${parsed.payload.version}`)
|
|
check(`${label}.productUuid`, parsed.payload.productUuid === expected.productUuid,
|
|
`got ${parsed.payload.productUuid}`)
|
|
check(`${label}.licenseUuid`, parsed.payload.licenseUuid === expected.licenseUuid,
|
|
`got ${parsed.payload.licenseUuid}`)
|
|
check(`${label}.issuedAt`, parsed.payload.issuedAt === expected.issuedAt)
|
|
check(`${label}.expiresAt`, parsed.payload.expiresAt === expected.expiresAt,
|
|
`got ${parsed.payload.expiresAt}`)
|
|
check(`${label}.flags`, parsed.payload.flags === expected.flags,
|
|
`got ${parsed.payload.flags}`)
|
|
check(`${label}.isFingerprintBound`,
|
|
parsed.payload.isFingerprintBound === expected.isFingerprintBound)
|
|
check(`${label}.isTrial`, parsed.payload.isTrial === expected.isTrial)
|
|
check(
|
|
`${label}.entitlements`,
|
|
JSON.stringify(parsed.payload.entitlements) === JSON.stringify(expected.entitlements),
|
|
`got ${JSON.stringify(parsed.payload.entitlements)}`,
|
|
)
|
|
check(
|
|
`${label}.fingerprintHash`,
|
|
toHex(parsed.payload.fingerprintHash) === expected.fingerprintHashHex,
|
|
)
|
|
|
|
// Signed bytes length depends on version + entitlements. We just assert
|
|
// that it round-trips the signature check.
|
|
check(`${label}.signature size`, parsed.signature.length === 64)
|
|
|
|
try {
|
|
const v = verifier.verify(caseData.licenseKey)
|
|
check(`${label}.verify()`, true)
|
|
check(`${label}.verify productId`, v.productId === expected.productUuid)
|
|
check(`${label}.verify licenseId`, v.licenseId === expected.licenseUuid)
|
|
} catch (e) {
|
|
check(`${label}.verify()`, false, String(e))
|
|
}
|
|
|
|
if (expected.isFingerprintBound) {
|
|
try {
|
|
verifier.verifyWithFingerprint(caseData.licenseKey, expected.fingerprintRaw)
|
|
check(`${label}.verifyWithFingerprint correct`, true)
|
|
} catch (e) {
|
|
check(`${label}.verifyWithFingerprint correct`, false, String(e))
|
|
}
|
|
let rejectedWrong = false
|
|
try {
|
|
verifier.verifyWithFingerprint(caseData.licenseKey, 'wrong-fingerprint')
|
|
} catch (e) {
|
|
rejectedWrong = String(e).toLowerCase().includes('fingerprint')
|
|
}
|
|
check(`${label}.verifyWithFingerprint wrong`, rejectedWrong)
|
|
}
|
|
|
|
// Entitlement helper.
|
|
for (const slug of expected.entitlements) {
|
|
check(`${label}.hasEntitlement('${slug}')`, hasEntitlement(parsed.payload, slug))
|
|
}
|
|
check(`${label}.hasEntitlement('nonexistent')`,
|
|
!hasEntitlement(parsed.payload, 'definitely-not-a-real-slug'))
|
|
|
|
// Expiry helper.
|
|
if (expected.expiresAt > 0) {
|
|
check(`${label}.isExpiredAt(before)`, !isExpiredAt(parsed.payload, expected.expiresAt - 1))
|
|
check(`${label}.isExpiredAt(at)`, isExpiredAt(parsed.payload, expected.expiresAt))
|
|
check(`${label}.isExpiredAt(after)`, isExpiredAt(parsed.payload, expected.expiresAt + 1))
|
|
} else {
|
|
check(`${label}.perpetual never expires`,
|
|
!isExpiredAt(parsed.payload, 2_000_000_000))
|
|
}
|
|
|
|
// Tamper check — flip the last base32 character of the signature.
|
|
const orig = caseData.licenseKey
|
|
const lastChar = orig.slice(-1)
|
|
const flipped = lastChar === 'A' ? 'B' : 'A'
|
|
const tampered = orig.slice(0, -1) + flipped
|
|
let tamperRejected = false
|
|
try {
|
|
verifier.verify(tampered)
|
|
} catch {
|
|
tamperRejected = true
|
|
}
|
|
check(`${label}.tampered key rejected`, tamperRejected)
|
|
}
|
|
|
|
runCase('v1', vector.v1)
|
|
runCase('v2', vector.v2)
|
|
runCase('v2_perpetual_unbound', vector.v2_perpetual_unbound)
|
|
|
|
console.log('\n== hashFingerprint === Python SHA-256 ==')
|
|
check(
|
|
'sha256 matches',
|
|
toHex(hashFingerprint(vector.v1.expected.fingerprintRaw))
|
|
=== vector.v1.expected.fingerprintHashHex,
|
|
)
|
|
|
|
console.log('\n== pubkey loaded has correct length ==')
|
|
check(
|
|
'public key raw is 32 bytes',
|
|
PublicKey.fromPem(vector.publicKeyPem).raw.length === 32,
|
|
)
|
|
|
|
if (failures > 0) {
|
|
console.log(`\nFAIL: ${failures} check(s) failed`)
|
|
process.exit(1)
|
|
}
|
|
console.log('\nALL CHECKS PASSED')
|