@moonbase.sh/licensing 2.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FINGERPRINT_SPEC.md +647 -0
- package/README.md +143 -6
- package/dist/index.cjs +599 -66
- package/dist/index.d.cts +367 -14
- package/dist/index.d.ts +367 -14
- package/dist/index.js +581 -67
- package/fingerprint-vectors.json +948 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
3
|
+
import process3 from "process";
|
|
4
|
+
|
|
1
5
|
// src/client.ts
|
|
2
6
|
import fetch from "cross-fetch";
|
|
3
7
|
|
|
8
|
+
// src/schemas.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
4
11
|
// src/types.ts
|
|
5
12
|
var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
|
|
6
13
|
ActivationMethod2["Online"] = "Online";
|
|
@@ -9,7 +16,6 @@ var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
|
|
|
9
16
|
})(ActivationMethod || {});
|
|
10
17
|
|
|
11
18
|
// src/schemas.ts
|
|
12
|
-
import { z } from "zod";
|
|
13
19
|
var activationRequestResponseSchema = z.object({
|
|
14
20
|
id: z.string(),
|
|
15
21
|
request: z.string(),
|
|
@@ -63,7 +69,7 @@ var LicenseClient = class {
|
|
|
63
69
|
deviceName: await this.deviceIdResolver.resolveDeviceName(),
|
|
64
70
|
deviceSignature: await this.deviceIdResolver.resolveDeviceId()
|
|
65
71
|
};
|
|
66
|
-
const response = await fetch(this.configuration.endpoint
|
|
72
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
|
|
67
73
|
...defaultFetchOptions,
|
|
68
74
|
method: "POST",
|
|
69
75
|
body: JSON.stringify(content)
|
|
@@ -85,7 +91,8 @@ var LicenseClient = class {
|
|
|
85
91
|
}
|
|
86
92
|
async getRequestedActivation(request) {
|
|
87
93
|
const response = await fetch(request.request, defaultFetchOptions);
|
|
88
|
-
if (response.status === 204 || response.status === 404)
|
|
94
|
+
if (response.status === 204 || response.status === 404)
|
|
95
|
+
return null;
|
|
89
96
|
return await this.handleLicenseResponse(response);
|
|
90
97
|
}
|
|
91
98
|
async requestTrial() {
|
|
@@ -93,7 +100,7 @@ var LicenseClient = class {
|
|
|
93
100
|
deviceName: await this.deviceIdResolver.resolveDeviceName(),
|
|
94
101
|
deviceSignature: await this.deviceIdResolver.resolveDeviceId()
|
|
95
102
|
};
|
|
96
|
-
const response = await fetch(this.configuration.endpoint
|
|
103
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
|
|
97
104
|
...defaultFetchOptions,
|
|
98
105
|
method: "POST",
|
|
99
106
|
headers: {
|
|
@@ -105,7 +112,7 @@ var LicenseClient = class {
|
|
|
105
112
|
return await this.handleLicenseResponse(response);
|
|
106
113
|
}
|
|
107
114
|
async validateLicense(license) {
|
|
108
|
-
const response = await fetch(this.configuration.endpoint
|
|
115
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
|
|
109
116
|
...defaultFetchOptions,
|
|
110
117
|
method: "POST",
|
|
111
118
|
headers: {
|
|
@@ -118,11 +125,12 @@ var LicenseClient = class {
|
|
|
118
125
|
}
|
|
119
126
|
async validateRawLicense(rawLicense) {
|
|
120
127
|
const license = await this.licenseValidator.validateLicense(rawLicense.toString("utf8"));
|
|
121
|
-
if (license.activationMethod === "Offline" /* Offline */)
|
|
128
|
+
if (license.activationMethod === "Offline" /* Offline */)
|
|
129
|
+
return license;
|
|
122
130
|
return await this.validateLicense(license);
|
|
123
131
|
}
|
|
124
132
|
async revokeLicense(license) {
|
|
125
|
-
const response = await fetch(this.configuration.endpoint
|
|
133
|
+
const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
|
|
126
134
|
...defaultFetchOptions,
|
|
127
135
|
method: "POST",
|
|
128
136
|
headers: {
|
|
@@ -144,11 +152,14 @@ var LicenseClient = class {
|
|
|
144
152
|
buildQueryString() {
|
|
145
153
|
const parts = ["format=JWT"];
|
|
146
154
|
const { platform, appVersion, metadata } = this.configuration;
|
|
147
|
-
if (platform)
|
|
148
|
-
|
|
155
|
+
if (platform)
|
|
156
|
+
parts.push(`platform=${encodeURIComponent(platform)}`);
|
|
157
|
+
if (appVersion)
|
|
158
|
+
parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
|
|
149
159
|
if (metadata) {
|
|
150
160
|
for (const [key, value] of Object.entries(metadata)) {
|
|
151
|
-
if (value === null || value === void 0 || value === "")
|
|
161
|
+
if (value === null || value === void 0 || value === "")
|
|
162
|
+
continue;
|
|
152
163
|
parts.push(`meta[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
@@ -157,32 +168,486 @@ var LicenseClient = class {
|
|
|
157
168
|
};
|
|
158
169
|
|
|
159
170
|
// src/deviceIdResolver.ts
|
|
171
|
+
import cp2 from "child_process";
|
|
172
|
+
import { createHash as createHash2 } from "crypto";
|
|
173
|
+
import os2 from "os";
|
|
174
|
+
import process2 from "process";
|
|
160
175
|
import si from "systeminformation";
|
|
161
|
-
|
|
176
|
+
|
|
177
|
+
// src/errors.ts
|
|
178
|
+
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
179
|
+
ErrorType2["None"] = "None";
|
|
180
|
+
ErrorType2["ApiError"] = "ApiError";
|
|
181
|
+
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
182
|
+
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
183
|
+
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
184
|
+
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
185
|
+
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
186
|
+
ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
|
|
187
|
+
ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
|
|
188
|
+
return ErrorType2;
|
|
189
|
+
})(ErrorType || {});
|
|
190
|
+
var MoonbaseError = class extends Error {
|
|
191
|
+
constructor(title, detail, type, inner) {
|
|
192
|
+
super();
|
|
193
|
+
this.title = title;
|
|
194
|
+
this.detail = detail;
|
|
195
|
+
this.type = type;
|
|
196
|
+
this.inner = inner;
|
|
197
|
+
this.name = "MoonbaseError";
|
|
198
|
+
this.message = detail != null ? detail : title;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var InsufficientDeviceIdentityError = class extends MoonbaseError {
|
|
202
|
+
constructor(platform, reason = "no identity parameter could be read") {
|
|
203
|
+
super(
|
|
204
|
+
"No device identity",
|
|
205
|
+
`Could not identify this device (platform: ${platform}): ${reason}`,
|
|
206
|
+
"DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
|
|
207
|
+
);
|
|
208
|
+
this.platform = platform;
|
|
209
|
+
this.name = "InsufficientDeviceIdentityError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// src/fingerprint.ts
|
|
214
|
+
import { Buffer } from "buffer";
|
|
162
215
|
import cp from "child_process";
|
|
163
216
|
import { createHash } from "crypto";
|
|
164
|
-
|
|
217
|
+
import fs from "fs";
|
|
218
|
+
import os from "os";
|
|
219
|
+
import process from "process";
|
|
220
|
+
var FINGERPRINT_PREFIX = "moonbase:fingerprint:v2";
|
|
221
|
+
var FINGERPRINT_VERSION = 2;
|
|
222
|
+
var MAX_VALUE_LENGTH = 128;
|
|
223
|
+
var PRINTABLE_ASCII_MIN = 32;
|
|
224
|
+
var PRINTABLE_ASCII_MAX = 126;
|
|
225
|
+
var SOURCE_TAGS = {
|
|
226
|
+
identity: "",
|
|
227
|
+
deviceName: "n",
|
|
228
|
+
scoped: "s"
|
|
229
|
+
};
|
|
230
|
+
var DEVICE_ID_SOURCE_TAGS = Object.freeze({ ...SOURCE_TAGS });
|
|
231
|
+
var SOURCE_BY_TAG = new Map(
|
|
232
|
+
Object.entries(SOURCE_TAGS).map(([source, tag]) => [tag, source])
|
|
233
|
+
);
|
|
234
|
+
var SCOPED_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
235
|
+
function identitySource(platform) {
|
|
236
|
+
return SCOPED_PLATFORMS.has(platform) ? "scoped" : "identity";
|
|
237
|
+
}
|
|
238
|
+
var STAMP_PATTERN = /^mbd(\d+)([a-z]*)_([0-9a-f]{64})$/;
|
|
239
|
+
function canonicalizeValue(value) {
|
|
240
|
+
var _a;
|
|
241
|
+
let printable = "";
|
|
242
|
+
for (const char of value.normalize("NFC")) {
|
|
243
|
+
const code = (_a = char.codePointAt(0)) != null ? _a : 0;
|
|
244
|
+
if (code >= PRINTABLE_ASCII_MIN && code <= PRINTABLE_ASCII_MAX)
|
|
245
|
+
printable += char;
|
|
246
|
+
}
|
|
247
|
+
return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
|
|
248
|
+
}
|
|
249
|
+
var PLATFORM_TAGS = /* @__PURE__ */ new Set(
|
|
250
|
+
["mac", "ios", "windows", "android", "linux", "bsd", "unknown"]
|
|
251
|
+
);
|
|
252
|
+
function isPlatformTag(value) {
|
|
253
|
+
return PLATFORM_TAGS.has(value);
|
|
254
|
+
}
|
|
255
|
+
function platformTag(platform = process.platform) {
|
|
256
|
+
switch (platform) {
|
|
257
|
+
case "darwin":
|
|
258
|
+
return "mac";
|
|
259
|
+
case "win32":
|
|
260
|
+
return "windows";
|
|
261
|
+
case "android":
|
|
262
|
+
return "android";
|
|
263
|
+
case "linux":
|
|
264
|
+
return "linux";
|
|
265
|
+
case "freebsd":
|
|
266
|
+
case "openbsd":
|
|
267
|
+
case "netbsd":
|
|
268
|
+
return "bsd";
|
|
269
|
+
default:
|
|
270
|
+
return isPlatformTag(platform) ? platform : "unknown";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
274
|
+
"ioPlatformUuid",
|
|
275
|
+
"machineId",
|
|
276
|
+
"systemUuid",
|
|
277
|
+
"baseboardSerialNumber",
|
|
278
|
+
"identifierForVendor",
|
|
279
|
+
"androidId",
|
|
280
|
+
"deviceName"
|
|
281
|
+
]);
|
|
282
|
+
var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
|
|
283
|
+
var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
|
|
284
|
+
"to be filled by o.e.m.",
|
|
285
|
+
// The same filler without the dots, which plenty of firmware writes instead.
|
|
286
|
+
"to be filled by oem",
|
|
287
|
+
"default string",
|
|
288
|
+
"system serial number",
|
|
289
|
+
"base board serial number",
|
|
290
|
+
"chassis serial number",
|
|
291
|
+
"not specified",
|
|
292
|
+
"not applicable",
|
|
293
|
+
"not available",
|
|
294
|
+
"none",
|
|
295
|
+
"unknown",
|
|
296
|
+
"invalid",
|
|
297
|
+
"n/a",
|
|
298
|
+
"0123456789",
|
|
299
|
+
// `machine-id(5)`: the literal marker systemd writes to say "no id yet",
|
|
300
|
+
// e.g. in an initrd or a golden image awaiting first boot. Every machine
|
|
301
|
+
// deployed from such an image reads it, so it is the opposite of an identifier.
|
|
302
|
+
"uninitialized"
|
|
303
|
+
]);
|
|
304
|
+
var IDENTITY_CONSTRAINTS = {
|
|
305
|
+
androidId: {
|
|
306
|
+
// Reading the *static field* `Settings.Secure.ANDROID_ID` instead of calling
|
|
307
|
+
// `getString` with it yields the key name "android_id", identical on every
|
|
308
|
+
// device, so a single activation would unlock a whole Android install base.
|
|
309
|
+
// That string is not hex, so this stops it reaching the material.
|
|
310
|
+
//
|
|
311
|
+
// The bound is 1,16 and not 16 because AOSP before 8.0 generated the value
|
|
312
|
+
// with `Long.toHexString`, which drops leading zeros — a strict 16 would
|
|
313
|
+
// reject legitimate ids on roughly one in sixteen pre-Oreo devices.
|
|
314
|
+
format: /^[0-9a-f]{1,16}$/,
|
|
315
|
+
// A real ANDROID_ID shared by a large batch of 2010-era devices whose
|
|
316
|
+
// `ro.serialno` was unset, seeding the generator identically on every unit.
|
|
317
|
+
// Valid hex, so the format rule cannot catch it.
|
|
318
|
+
rejected: /* @__PURE__ */ new Set(["9774d56d682e549c"])
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function isNotProgrammed(value) {
|
|
322
|
+
return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
|
|
323
|
+
}
|
|
324
|
+
function isUsableIdentity(name, value) {
|
|
325
|
+
var _a, _b, _c;
|
|
326
|
+
if (isNotProgrammed(value))
|
|
327
|
+
return false;
|
|
328
|
+
const constraint = IDENTITY_CONSTRAINTS[name];
|
|
329
|
+
if (!constraint)
|
|
330
|
+
return true;
|
|
331
|
+
return ((_b = (_a = constraint.format) == null ? void 0 : _a.test(value)) != null ? _b : true) && !((_c = constraint.rejected) == null ? void 0 : _c.has(value.toLowerCase()));
|
|
332
|
+
}
|
|
333
|
+
function canonicalizeParams(params) {
|
|
334
|
+
const kept = [];
|
|
335
|
+
const seen = /* @__PURE__ */ new Set();
|
|
336
|
+
for (const [name, rawValue] of params) {
|
|
337
|
+
const value = canonicalizeValue(rawValue);
|
|
338
|
+
if (value.length === 0)
|
|
339
|
+
continue;
|
|
340
|
+
if (IDENTIFYING_PARAMS.has(name) && !isUsableIdentity(name, value))
|
|
341
|
+
continue;
|
|
342
|
+
if (seen.has(name))
|
|
343
|
+
throw new Error(`Duplicate fingerprint parameter name: ${name}`);
|
|
344
|
+
seen.add(name);
|
|
345
|
+
kept.push([name, value]);
|
|
346
|
+
}
|
|
347
|
+
return kept;
|
|
348
|
+
}
|
|
349
|
+
function buildFingerprintMaterial(platform, params) {
|
|
350
|
+
const kept = canonicalizeParams(params);
|
|
351
|
+
if (kept.length === 0)
|
|
352
|
+
throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
|
|
353
|
+
if (SCOPED_PLATFORMS.has(platform) && kept.some(([name]) => name === "deviceName")) {
|
|
354
|
+
throw new InsufficientDeviceIdentityError(
|
|
355
|
+
platform,
|
|
356
|
+
"the host-name fallback is not available on this platform, where the host name is the same on every device"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
|
|
360
|
+
throw new InsufficientDeviceIdentityError(
|
|
361
|
+
platform,
|
|
362
|
+
`only model-level parameters could be read (${kept.map(([name]) => name).join(", ")}), none of which identify this individual machine`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const lines = [FINGERPRINT_PREFIX, `platform=${platform}`];
|
|
366
|
+
for (const [name, value] of kept)
|
|
367
|
+
lines.push(`${name}=${value}`);
|
|
368
|
+
return lines.join("\n");
|
|
369
|
+
}
|
|
370
|
+
function fingerprintDigest(material) {
|
|
371
|
+
return createHash("sha256").update(material, "utf8").digest("hex");
|
|
372
|
+
}
|
|
373
|
+
function stampDeviceId(digest, source = "identity") {
|
|
374
|
+
return `mbd${FINGERPRINT_VERSION}${SOURCE_TAGS[source]}_${digest}`;
|
|
375
|
+
}
|
|
376
|
+
function fingerprintDeviceId(material, source = "identity") {
|
|
377
|
+
return stampDeviceId(fingerprintDigest(material), source);
|
|
378
|
+
}
|
|
379
|
+
function parseDeviceIdStamp(deviceId) {
|
|
380
|
+
var _a;
|
|
381
|
+
const match = STAMP_PATTERN.exec(deviceId);
|
|
382
|
+
if (!match)
|
|
383
|
+
return null;
|
|
384
|
+
return {
|
|
385
|
+
version: Number(match[1]),
|
|
386
|
+
sourceTag: match[2],
|
|
387
|
+
source: (_a = SOURCE_BY_TAG.get(match[2])) != null ? _a : null,
|
|
388
|
+
digest: match[3]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function commandOutput(command) {
|
|
392
|
+
try {
|
|
393
|
+
return cp.execSync(command, { encoding: "utf8" });
|
|
394
|
+
} catch (e) {
|
|
395
|
+
return "";
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function readSysFile(path2) {
|
|
399
|
+
try {
|
|
400
|
+
return fs.readFileSync(path2, "utf8");
|
|
401
|
+
} catch (e) {
|
|
402
|
+
return "";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function parseIoregPlatformUuid(ioregOutput) {
|
|
406
|
+
const match = ioregOutput.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
407
|
+
return match ? match[1].replaceAll("-", "").toUpperCase() : "";
|
|
408
|
+
}
|
|
409
|
+
function readMacIdentity() {
|
|
410
|
+
const uuid = parseIoregPlatformUuid(commandOutput("ioreg -rd1 -c IOPlatformExpertDevice"));
|
|
411
|
+
const params = [["ioPlatformUuid", uuid]];
|
|
412
|
+
return { params, deviceName: os.hostname().replace(/\.local$/i, "") };
|
|
413
|
+
}
|
|
414
|
+
function selectMachineId(...sources) {
|
|
415
|
+
var _a;
|
|
416
|
+
return (_a = sources.map(canonicalizeValue).find((value) => /^[0-9a-f]{32}$/.test(value) && !isNotProgrammed(value))) != null ? _a : "";
|
|
417
|
+
}
|
|
418
|
+
function readMachineId() {
|
|
419
|
+
return selectMachineId(
|
|
420
|
+
readSysFile("/etc/machine-id"),
|
|
421
|
+
readSysFile("/var/lib/dbus/machine-id")
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
function readLinuxIdentity() {
|
|
425
|
+
const params = [
|
|
426
|
+
["machineId", readMachineId()],
|
|
427
|
+
["sysVendor", readSysFile("/sys/class/dmi/id/sys_vendor")],
|
|
428
|
+
["productName", readSysFile("/sys/class/dmi/id/product_name")],
|
|
429
|
+
["boardVendor", readSysFile("/sys/class/dmi/id/board_vendor")],
|
|
430
|
+
["boardName", readSysFile("/sys/class/dmi/id/board_name")]
|
|
431
|
+
];
|
|
432
|
+
return { params, deviceName: os.hostname() };
|
|
433
|
+
}
|
|
434
|
+
function parseSmbiosStructures(data) {
|
|
435
|
+
const structures = [];
|
|
436
|
+
let offset = 0;
|
|
437
|
+
while (offset + 4 <= data.length) {
|
|
438
|
+
const type = data[offset];
|
|
439
|
+
const length = data[offset + 1];
|
|
440
|
+
if (length < 4 || offset + length > data.length)
|
|
441
|
+
break;
|
|
442
|
+
const formatted = data.subarray(offset, offset + length);
|
|
443
|
+
const strings = [];
|
|
444
|
+
let p = offset + length;
|
|
445
|
+
if (p + 1 < data.length && data[p] === 0 && data[p + 1] === 0) {
|
|
446
|
+
p += 2;
|
|
447
|
+
} else {
|
|
448
|
+
while (p < data.length) {
|
|
449
|
+
let end = p;
|
|
450
|
+
while (end < data.length && data[end] !== 0)
|
|
451
|
+
end++;
|
|
452
|
+
strings.push(data.toString("latin1", p, end));
|
|
453
|
+
p = end + 1;
|
|
454
|
+
if (p < data.length && data[p] === 0) {
|
|
455
|
+
p += 1;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
structures.push({ type, formatted, strings });
|
|
461
|
+
if (type === 127)
|
|
462
|
+
break;
|
|
463
|
+
offset = p;
|
|
464
|
+
}
|
|
465
|
+
return structures;
|
|
466
|
+
}
|
|
467
|
+
function resolveSmbiosString(structure, fieldOffset) {
|
|
468
|
+
if (fieldOffset >= structure.formatted.length)
|
|
469
|
+
return "";
|
|
470
|
+
const index = structure.formatted[fieldOffset];
|
|
471
|
+
if (index === 0 || index > structure.strings.length)
|
|
472
|
+
return "";
|
|
473
|
+
return structure.strings[index - 1];
|
|
474
|
+
}
|
|
475
|
+
function formatSmbiosUuid(structure, fieldOffset) {
|
|
476
|
+
if (fieldOffset + 16 > structure.formatted.length)
|
|
477
|
+
return "";
|
|
478
|
+
const bytes = structure.formatted.subarray(fieldOffset, fieldOffset + 16);
|
|
479
|
+
if (bytes.every((byte) => byte === 0) || bytes.every((byte) => byte === 255))
|
|
480
|
+
return "";
|
|
481
|
+
let hex = "";
|
|
482
|
+
for (const byte of bytes)
|
|
483
|
+
hex += byte.toString(16).padStart(2, "0").toUpperCase();
|
|
484
|
+
return hex;
|
|
485
|
+
}
|
|
486
|
+
function parseSmbiosParams(smbiosData) {
|
|
487
|
+
const structures = parseSmbiosStructures(smbiosData);
|
|
488
|
+
const params = [];
|
|
489
|
+
const system = structures.find((structure) => structure.type === 1);
|
|
490
|
+
if (system) {
|
|
491
|
+
params.push(["systemManufacturer", resolveSmbiosString(system, 4)]);
|
|
492
|
+
params.push(["systemProductName", resolveSmbiosString(system, 5)]);
|
|
493
|
+
params.push(["systemUuid", formatSmbiosUuid(system, 8)]);
|
|
494
|
+
}
|
|
495
|
+
const baseboard = structures.find((structure) => structure.type === 2);
|
|
496
|
+
if (baseboard) {
|
|
497
|
+
params.push(["baseboardManufacturer", resolveSmbiosString(baseboard, 4)]);
|
|
498
|
+
params.push(["baseboardProduct", resolveSmbiosString(baseboard, 5)]);
|
|
499
|
+
params.push(["baseboardSerialNumber", resolveSmbiosString(baseboard, 7)]);
|
|
500
|
+
}
|
|
501
|
+
return params;
|
|
502
|
+
}
|
|
503
|
+
function readWindowsSmbios() {
|
|
504
|
+
const script = "[Convert]::ToBase64String((Get-CimInstance -Namespace root/wmi -ClassName MSSmBios_RawSMBiosTables).SMBiosData)";
|
|
505
|
+
const output = commandOutput(`powershell -NoProfile -NonInteractive -Command "${script}"`);
|
|
506
|
+
try {
|
|
507
|
+
return Buffer.from(output.trim(), "base64");
|
|
508
|
+
} catch (e) {
|
|
509
|
+
return Buffer.alloc(0);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function readWindowsIdentity() {
|
|
513
|
+
return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: os.hostname() };
|
|
514
|
+
}
|
|
515
|
+
function defaultDeviceIdentityReader(platform = process.platform) {
|
|
516
|
+
const tag = platformTag(platform);
|
|
517
|
+
return {
|
|
518
|
+
read() {
|
|
519
|
+
switch (tag) {
|
|
520
|
+
case "mac":
|
|
521
|
+
return readMacIdentity();
|
|
522
|
+
case "linux":
|
|
523
|
+
return readLinuxIdentity();
|
|
524
|
+
case "windows":
|
|
525
|
+
return readWindowsIdentity();
|
|
526
|
+
// Everything else yields no identity params here, so it resolves to an
|
|
527
|
+
// insufficient-identity error unless the deviceName fallback is enabled.
|
|
528
|
+
// For bsd and unknown that matches the spec, which defines none. Android
|
|
529
|
+
// is different: the spec *does* define `androidId`, but it comes from
|
|
530
|
+
// `Settings.Secure.getString`, which is Android-framework API that a
|
|
531
|
+
// Node.js process cannot reach. Collecting it is the C++/.NET SDKs' job.
|
|
532
|
+
// A host that can bridge it should supply its own DeviceIdentityReader.
|
|
533
|
+
default:
|
|
534
|
+
return { params: [], deviceName: os.hostname() };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/deviceIdResolver.ts
|
|
541
|
+
var MoonbaseDeviceIdResolver = class {
|
|
542
|
+
constructor(options) {
|
|
543
|
+
var _a, _b, _c;
|
|
544
|
+
const platform = (_a = options == null ? void 0 : options.platform) != null ? _a : process2.platform;
|
|
545
|
+
this.platform = platformTag(platform);
|
|
546
|
+
this.reader = (_b = options == null ? void 0 : options.reader) != null ? _b : defaultDeviceIdentityReader(platform);
|
|
547
|
+
this.fallback = (_c = options == null ? void 0 : options.fallback) != null ? _c : "none";
|
|
548
|
+
}
|
|
549
|
+
async resolveDeviceName() {
|
|
550
|
+
return this.readIdentity().deviceName;
|
|
551
|
+
}
|
|
552
|
+
async resolveDeviceId() {
|
|
553
|
+
return this.compute().deviceId;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* A fresh copy each call. The device binding is what activation sends and what
|
|
557
|
+
* validation compares, so handing out the resolver's own object would let a
|
|
558
|
+
* consumer that edits a diagnostic — or logs it through something that
|
|
559
|
+
* normalizes in place — silently change the id every later call returns.
|
|
560
|
+
*/
|
|
561
|
+
async describeDevice() {
|
|
562
|
+
const described = this.compute();
|
|
563
|
+
return { ...described, paramNames: [...described.paramNames] };
|
|
564
|
+
}
|
|
565
|
+
compute() {
|
|
566
|
+
var _a;
|
|
567
|
+
(_a = this.described) != null ? _a : this.described = this.computeDescription();
|
|
568
|
+
return this.described;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Read identity at most once. Both halves of an activation request ask for it —
|
|
572
|
+
* the name and then the id — and a read can mean spawning `ioreg` or
|
|
573
|
+
* PowerShell, so reading per call would double the cost of every request and
|
|
574
|
+
* let the name and the id come from two different reads of the machine.
|
|
575
|
+
*/
|
|
576
|
+
readIdentity() {
|
|
577
|
+
var _a;
|
|
578
|
+
(_a = this.identity) != null ? _a : this.identity = this.reader.read();
|
|
579
|
+
return this.identity;
|
|
580
|
+
}
|
|
581
|
+
computeDescription() {
|
|
582
|
+
const { params, deviceName } = this.readIdentity();
|
|
583
|
+
try {
|
|
584
|
+
return this.describe(params, identitySource(this.platform));
|
|
585
|
+
} catch (err) {
|
|
586
|
+
if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
|
|
587
|
+
throw err;
|
|
588
|
+
return this.describe([["deviceName", deviceName]], "deviceName");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
describe(params, source) {
|
|
592
|
+
return {
|
|
593
|
+
deviceId: fingerprintDeviceId(buildFingerprintMaterial(this.platform, params), source),
|
|
594
|
+
version: FINGERPRINT_VERSION,
|
|
595
|
+
platform: this.platform,
|
|
596
|
+
source,
|
|
597
|
+
paramNames: canonicalizeParams(params).map(([name]) => name)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
var MigratingDeviceIdResolver = class {
|
|
602
|
+
constructor(current, ...previous) {
|
|
603
|
+
this.current = current;
|
|
604
|
+
this.previous = previous;
|
|
605
|
+
const describe = current.describeDevice;
|
|
606
|
+
if (typeof describe === "function")
|
|
607
|
+
this.describeDevice = () => describe.call(current);
|
|
608
|
+
}
|
|
609
|
+
async resolveDeviceName() {
|
|
610
|
+
return this.current.resolveDeviceName();
|
|
611
|
+
}
|
|
612
|
+
async resolveDeviceId() {
|
|
613
|
+
return this.current.resolveDeviceId();
|
|
614
|
+
}
|
|
615
|
+
async acceptsDeviceId(deviceId) {
|
|
616
|
+
if (!this.previousIds) {
|
|
617
|
+
this.previousIds = Promise.all(this.previous.map(async (resolver) => {
|
|
618
|
+
try {
|
|
619
|
+
return await resolver.resolveDeviceId();
|
|
620
|
+
} catch (e) {
|
|
621
|
+
return "";
|
|
622
|
+
}
|
|
623
|
+
}));
|
|
624
|
+
}
|
|
625
|
+
return (await this.previousIds).some((previous) => previous.length > 0 && previous === deviceId);
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
var LegacyDeviceIdResolver = class {
|
|
165
629
|
async resolveDeviceName() {
|
|
166
|
-
switch (
|
|
630
|
+
switch (process2.platform) {
|
|
167
631
|
case "darwin":
|
|
168
632
|
try {
|
|
169
|
-
return
|
|
633
|
+
return cp2.execSync("scutil --get ComputerName").toString().trim();
|
|
170
634
|
} catch (e) {
|
|
171
|
-
return
|
|
635
|
+
return os2.hostname();
|
|
172
636
|
}
|
|
173
637
|
case "win32":
|
|
174
|
-
return
|
|
175
|
-
case "linux":
|
|
176
|
-
const prettyname =
|
|
177
|
-
return prettyname ||
|
|
638
|
+
return process2.env.COMPUTERNAME || os2.hostname();
|
|
639
|
+
case "linux": {
|
|
640
|
+
const prettyname = cp2.execSync("hostnamectl --pretty").toString().trim();
|
|
641
|
+
return prettyname || os2.hostname();
|
|
642
|
+
}
|
|
178
643
|
default:
|
|
179
|
-
return
|
|
644
|
+
return os2.hostname();
|
|
180
645
|
}
|
|
181
646
|
}
|
|
182
647
|
async resolveDeviceId() {
|
|
183
648
|
const [cpu, hw] = await Promise.all([si.cpu(), si.system()]);
|
|
184
649
|
const parts = [
|
|
185
|
-
|
|
650
|
+
os2.hostname(),
|
|
186
651
|
hw.manufacturer,
|
|
187
652
|
hw.model,
|
|
188
653
|
hw.serial,
|
|
@@ -194,40 +659,15 @@ var DefaultDeviceIdResolver = class {
|
|
|
194
659
|
cpu.family,
|
|
195
660
|
cpu.model
|
|
196
661
|
];
|
|
197
|
-
const hash =
|
|
662
|
+
const hash = createHash2("sha256").update(parts.join("")).digest();
|
|
198
663
|
return hash.toString("base64").replaceAll("=", "");
|
|
199
664
|
}
|
|
200
665
|
};
|
|
201
666
|
|
|
202
667
|
// src/store.ts
|
|
203
668
|
import fsSync from "fs";
|
|
204
|
-
import
|
|
669
|
+
import fs2 from "fs/promises";
|
|
205
670
|
import path from "path";
|
|
206
|
-
|
|
207
|
-
// src/errors.ts
|
|
208
|
-
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
209
|
-
ErrorType2["None"] = "None";
|
|
210
|
-
ErrorType2["ApiError"] = "ApiError";
|
|
211
|
-
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
212
|
-
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
213
|
-
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
214
|
-
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
215
|
-
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
216
|
-
return ErrorType2;
|
|
217
|
-
})(ErrorType || {});
|
|
218
|
-
var MoonbaseError = class extends Error {
|
|
219
|
-
constructor(title, detail, type, inner) {
|
|
220
|
-
super();
|
|
221
|
-
this.title = title;
|
|
222
|
-
this.detail = detail;
|
|
223
|
-
this.type = type;
|
|
224
|
-
this.inner = inner;
|
|
225
|
-
this.name = "MoonbaseError";
|
|
226
|
-
this.message = detail != null ? detail : title;
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// src/store.ts
|
|
231
671
|
var InMemoryLicenseStore = class {
|
|
232
672
|
async loadLocalLicense() {
|
|
233
673
|
var _a;
|
|
@@ -245,9 +685,10 @@ var FileLicenseStore = class {
|
|
|
245
685
|
this.options = options;
|
|
246
686
|
}
|
|
247
687
|
async loadLocalLicense() {
|
|
248
|
-
if (!fsSync.existsSync(this.path))
|
|
688
|
+
if (!fsSync.existsSync(this.path))
|
|
689
|
+
return null;
|
|
249
690
|
try {
|
|
250
|
-
const file = await
|
|
691
|
+
const file = await fs2.readFile(this.path, { encoding: "utf-8" });
|
|
251
692
|
const license = licenseSchema.parse(JSON.parse(file));
|
|
252
693
|
return license;
|
|
253
694
|
} catch (err) {
|
|
@@ -255,11 +696,12 @@ var FileLicenseStore = class {
|
|
|
255
696
|
}
|
|
256
697
|
}
|
|
257
698
|
async storeLocalLicense(license) {
|
|
258
|
-
await
|
|
699
|
+
await fs2.writeFile(this.path, JSON.stringify(license), { encoding: "utf-8" });
|
|
259
700
|
}
|
|
260
701
|
async deleteLocalLicense() {
|
|
261
|
-
if (!fsSync.existsSync(this.path))
|
|
262
|
-
|
|
702
|
+
if (!fsSync.existsSync(this.path))
|
|
703
|
+
return;
|
|
704
|
+
await fs2.rm(this.path);
|
|
263
705
|
}
|
|
264
706
|
get path() {
|
|
265
707
|
var _a, _b, _c, _d;
|
|
@@ -268,8 +710,8 @@ var FileLicenseStore = class {
|
|
|
268
710
|
};
|
|
269
711
|
|
|
270
712
|
// src/validator.ts
|
|
271
|
-
import { z as z2 } from "zod";
|
|
272
713
|
import jwt from "jsonwebtoken";
|
|
714
|
+
import { z as z2 } from "zod";
|
|
273
715
|
var licenseClaimsSchema = z2.object({
|
|
274
716
|
["l:id" /* LicenseId */]: z2.string(),
|
|
275
717
|
["trial" /* Trial */]: z2.string().or(z2.boolean()).transform((v) => v === true || v === "true").pipe(z2.boolean()),
|
|
@@ -306,15 +748,20 @@ var LicenseValidator = class {
|
|
|
306
748
|
);
|
|
307
749
|
}
|
|
308
750
|
const expectedSignature = await this.deviceIdResolver.resolveDeviceId();
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"This license is not for this device",
|
|
313
|
-
"LicenseExpired" /* LicenseExpired */
|
|
314
|
-
);
|
|
315
|
-
}
|
|
751
|
+
const boundSignature = claims["sig" /* ComputerSignature */];
|
|
752
|
+
if (expectedSignature !== boundSignature && !await this.acceptsHistoricalDeviceId(boundSignature))
|
|
753
|
+
throw deviceMismatchError(expectedSignature, boundSignature);
|
|
316
754
|
return license;
|
|
317
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Give a {@link IMigratingDeviceIdResolver} the chance to vouch for a device id
|
|
758
|
+
* this machine used to have. Only consulted after the fast path fails, so
|
|
759
|
+
* apps that have not opted into a migration pay nothing.
|
|
760
|
+
*/
|
|
761
|
+
async acceptsHistoricalDeviceId(boundSignature) {
|
|
762
|
+
const accepts = this.deviceIdResolver.acceptsDeviceId;
|
|
763
|
+
return typeof accepts === "function" && await accepts.call(this.deviceIdResolver, boundSignature);
|
|
764
|
+
}
|
|
318
765
|
parseLicenseToken(token) {
|
|
319
766
|
return new Promise((resolve, reject) => {
|
|
320
767
|
jwt.verify(token, this.configuration.publicKey, {
|
|
@@ -379,11 +826,57 @@ var LicenseValidator = class {
|
|
|
379
826
|
});
|
|
380
827
|
}
|
|
381
828
|
};
|
|
829
|
+
function deviceMismatchError(expected, bound) {
|
|
830
|
+
const detail = "This license is not for this device";
|
|
831
|
+
const stampNote = describeStampDifference(expected, bound);
|
|
832
|
+
return new MoonbaseError(
|
|
833
|
+
"License is for another device",
|
|
834
|
+
stampNote ? `${detail}. ${stampNote}` : detail,
|
|
835
|
+
"LicenseDeviceMismatch" /* LicenseDeviceMismatch */
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
function describeStampDifference(expected, bound) {
|
|
839
|
+
const expectedStamp = parseDeviceIdStamp(expected);
|
|
840
|
+
if (!expectedStamp)
|
|
841
|
+
return null;
|
|
842
|
+
const boundStamp = parseDeviceIdStamp(bound);
|
|
843
|
+
if (!boundStamp || boundStamp.version !== expectedStamp.version)
|
|
844
|
+
return describeVersionDifference(expectedStamp, boundStamp);
|
|
845
|
+
if (boundStamp.sourceTag !== expectedStamp.sourceTag)
|
|
846
|
+
return describeSourceDifference(expectedStamp, boundStamp);
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
function describeVersionDifference(expected, bound) {
|
|
850
|
+
if (bound && bound.version > expected.version) {
|
|
851
|
+
return `The binding was created by device fingerprint v${bound.version}, which is newer than the v${expected.version} this SDK computes \u2014 update the SDK rather than re-activating, which would rebind the device to the older algorithm.`;
|
|
852
|
+
}
|
|
853
|
+
const boundVersion = bound ? `device fingerprint v${bound.version}` : "an SDK predating versioned device fingerprints";
|
|
854
|
+
return `The binding was created by ${boundVersion}, while this SDK computes v${expected.version}, so this may instead be the same machine bound under the older algorithm \u2014 re-activate to find out, or configure a MigratingDeviceIdResolver to keep accepting the previous id.`;
|
|
855
|
+
}
|
|
856
|
+
function describeSourceDifference(expected, bound) {
|
|
857
|
+
if (bound.source === "scoped") {
|
|
858
|
+
return "The binding uses an app-scoped device identity, which cannot be compared with the id this SDK computes \u2014 not even on the same device. Re-activate here to bind this build.";
|
|
859
|
+
}
|
|
860
|
+
if (expected.source === "scoped") {
|
|
861
|
+
return "This SDK computes an app-scoped device identity, which cannot be compared with the one the binding carries \u2014 not even on the same device. Re-activate here to bind this app.";
|
|
862
|
+
}
|
|
863
|
+
if (bound.source === null) {
|
|
864
|
+
return `The binding carries the device identity tag "${bound.sourceTag}", which this SDK does not recognise \u2014 it was created by a newer Moonbase SDK, so update rather than re-activating.`;
|
|
865
|
+
}
|
|
866
|
+
if (expected.source === null) {
|
|
867
|
+
return `The id this SDK computes carries the device identity tag "${expected.sourceTag}", which the spec does not define \u2014 it came from a custom device id resolver, so check that resolver rather than the binding.`;
|
|
868
|
+
}
|
|
869
|
+
if (bound.source === "deviceName") {
|
|
870
|
+
return "The binding was created from the host-name fallback, while this SDK reads hardware identity, so this may instead be the same machine bound while no hardware identity could be read \u2014 re-activate to find out.";
|
|
871
|
+
}
|
|
872
|
+
return "The binding was created from hardware identity, while this SDK has fallen back to the host name \u2014 check why hardware identity cannot be read here rather than re-activating, which would rebind the device to the weaker id.";
|
|
873
|
+
}
|
|
382
874
|
|
|
383
875
|
// src/index.ts
|
|
384
876
|
function detectPlatform() {
|
|
385
|
-
if (typeof
|
|
386
|
-
|
|
877
|
+
if (typeof process3 === "undefined")
|
|
878
|
+
return void 0;
|
|
879
|
+
switch (process3.platform) {
|
|
387
880
|
case "darwin":
|
|
388
881
|
return "Mac";
|
|
389
882
|
case "win32":
|
|
@@ -403,7 +896,7 @@ var MoonbaseLicensing = class {
|
|
|
403
896
|
platform: configuration.platform === void 0 ? detectPlatform() : configuration.platform
|
|
404
897
|
};
|
|
405
898
|
this.store = (_a = configuration.licenseStore) != null ? _a : new InMemoryLicenseStore();
|
|
406
|
-
this.deviceIdResolver = (_b = configuration.
|
|
899
|
+
this.deviceIdResolver = (_b = configuration.deviceIdResolver) != null ? _b : new MoonbaseDeviceIdResolver();
|
|
407
900
|
this.validator = new LicenseValidator(this.configuration, this.deviceIdResolver);
|
|
408
901
|
this.client = new LicenseClient(this.configuration, this.deviceIdResolver, this.validator);
|
|
409
902
|
}
|
|
@@ -415,7 +908,7 @@ var MoonbaseLicensing = class {
|
|
|
415
908
|
format: "JWT"
|
|
416
909
|
};
|
|
417
910
|
const json = JSON.stringify(token);
|
|
418
|
-
return
|
|
911
|
+
return Buffer2.from(Buffer2.from(json).toString("base64"));
|
|
419
912
|
}
|
|
420
913
|
async readRawLicense(license) {
|
|
421
914
|
return await this.client.validateRawLicense(license);
|
|
@@ -423,12 +916,33 @@ var MoonbaseLicensing = class {
|
|
|
423
916
|
};
|
|
424
917
|
export {
|
|
425
918
|
ActivationMethod,
|
|
426
|
-
|
|
919
|
+
DEVICE_ID_SOURCE_TAGS,
|
|
427
920
|
ErrorType,
|
|
921
|
+
FINGERPRINT_PREFIX,
|
|
922
|
+
FINGERPRINT_VERSION,
|
|
428
923
|
FileLicenseStore,
|
|
924
|
+
IDENTIFYING_PARAM_NAMES,
|
|
429
925
|
InMemoryLicenseStore,
|
|
926
|
+
InsufficientDeviceIdentityError,
|
|
927
|
+
LegacyDeviceIdResolver,
|
|
430
928
|
LicenseClient,
|
|
431
929
|
LicenseValidator,
|
|
930
|
+
MAX_VALUE_LENGTH,
|
|
931
|
+
MigratingDeviceIdResolver,
|
|
932
|
+
MoonbaseDeviceIdResolver,
|
|
432
933
|
MoonbaseError,
|
|
433
|
-
MoonbaseLicensing
|
|
934
|
+
MoonbaseLicensing,
|
|
935
|
+
buildFingerprintMaterial,
|
|
936
|
+
canonicalizeParams,
|
|
937
|
+
canonicalizeValue,
|
|
938
|
+
defaultDeviceIdentityReader,
|
|
939
|
+
fingerprintDeviceId,
|
|
940
|
+
fingerprintDigest,
|
|
941
|
+
identitySource,
|
|
942
|
+
parseDeviceIdStamp,
|
|
943
|
+
parseIoregPlatformUuid,
|
|
944
|
+
parseSmbiosParams,
|
|
945
|
+
platformTag,
|
|
946
|
+
selectMachineId,
|
|
947
|
+
stampDeviceId
|
|
434
948
|
};
|