@moonbase.sh/licensing 3.0.0 → 3.1.1
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 +241 -8
- package/README.md +69 -11
- package/dist/index.cjs +180 -59
- package/dist/index.d.cts +88 -20
- package/dist/index.d.ts +88 -20
- package/dist/index.js +178 -59
- package/fingerprint-vectors.json +204 -0
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -33,11 +33,30 @@ declare const FINGERPRINT_VERSION = 2;
|
|
|
33
33
|
declare const MAX_VALUE_LENGTH = 128;
|
|
34
34
|
/**
|
|
35
35
|
* What the material was built from. `identity` is the real hardware fingerprint;
|
|
36
|
-
* `deviceName` is the opt-in, deliberately weaker host-name fallback
|
|
37
|
-
*
|
|
36
|
+
* `deviceName` is the opt-in, deliberately weaker host-name fallback; `scoped` is
|
|
37
|
+
* an id that is stable only within one app scope (iOS/Android, where the platform
|
|
38
|
+
* exposes nothing an unrelated app can read). Each is stamped distinctly so a
|
|
39
|
+
* server can tell them apart.
|
|
38
40
|
*/
|
|
39
|
-
type DeviceIdSource = 'identity' | 'deviceName';
|
|
40
|
-
|
|
41
|
+
type DeviceIdSource = 'identity' | 'deviceName' | 'scoped';
|
|
42
|
+
/** The tags this version defines, as a detached frozen map (see {@link IDENTIFYING_PARAM_NAMES}). */
|
|
43
|
+
declare const DEVICE_ID_SOURCE_TAGS: Readonly<Record<DeviceIdSource, string>>;
|
|
44
|
+
type PlatformTag = 'mac' | 'ios' | 'windows' | 'android' | 'linux' | 'bsd' | 'unknown';
|
|
45
|
+
/**
|
|
46
|
+
* The source a successful identity read earns on this platform.
|
|
47
|
+
*
|
|
48
|
+
* Scoped platforms expose no identifier an unrelated app can read, so anything
|
|
49
|
+
* built from their parameters is scoped to the app and must be stamped `mbd2s_`.
|
|
50
|
+
* Stamping it `mbd2_` would tell a server the id is a hardware fingerprint
|
|
51
|
+
* comparable across every app on the device, which is exactly what it is not:
|
|
52
|
+
* the server would then be entitled to correlate ids the spec forbids
|
|
53
|
+
* correlating, and a diagnostic would offer remedies that cannot apply.
|
|
54
|
+
*
|
|
55
|
+
* Derived from the platform rather than chosen by the caller, so a host that
|
|
56
|
+
* bridges `identifierForVendor` or `androidId` through a custom
|
|
57
|
+
* {@link DeviceIdentityReader} cannot accidentally mislabel it.
|
|
58
|
+
*/
|
|
59
|
+
declare function identitySource(platform: PlatformTag): DeviceIdSource;
|
|
41
60
|
type FingerprintParam = readonly [name: string, value: string];
|
|
42
61
|
interface DeviceIdentity {
|
|
43
62
|
/** Ordered identity params. Empty values are dropped by the material builder. */
|
|
@@ -53,7 +72,10 @@ interface DeviceIdentityReader {
|
|
|
53
72
|
interface DeviceIdStamp {
|
|
54
73
|
/** Fingerprint spec version that produced the digest. */
|
|
55
74
|
version: number;
|
|
56
|
-
source:
|
|
75
|
+
/** The literal source tag: `''`, `'n'`, `'s'`, or one a newer SDK introduced. */
|
|
76
|
+
sourceTag: string;
|
|
77
|
+
/** What {@link sourceTag} means, or `null` when this SDK does not define that tag. */
|
|
78
|
+
source: DeviceIdSource | null;
|
|
57
79
|
/** The 64-char lowercase-hex SHA-256. */
|
|
58
80
|
digest: string;
|
|
59
81
|
}
|
|
@@ -69,8 +91,23 @@ interface DeviceIdStamp {
|
|
|
69
91
|
* decodings disagree about is discarded either way.
|
|
70
92
|
*/
|
|
71
93
|
declare function canonicalizeValue(value: string): string;
|
|
72
|
-
/**
|
|
73
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Map a Node.js `process.platform` value onto a canonical platform tag, or pass
|
|
96
|
+
* through a value that is already one.
|
|
97
|
+
*
|
|
98
|
+
* The passthrough is what makes the mobile platforms reachable at all. Node does
|
|
99
|
+
* not run on iOS, so `process.platform` is never `'ios'` and no amount of mapping
|
|
100
|
+
* can produce that tag — yet a host embedding Node can bridge
|
|
101
|
+
* `identifierForVendor` from platform API. Without this, such a host has no way
|
|
102
|
+
* to say which platform it is on: the value would fall through to `'unknown'`,
|
|
103
|
+
* and the material would carry `platform=unknown`, so the digest would not match
|
|
104
|
+
* what a conforming iOS SDK computes on the same device, and the id would be
|
|
105
|
+
* stamped `mbd2_` rather than `mbd2s_`. Both wrong, and both silent.
|
|
106
|
+
*
|
|
107
|
+
* `'android'` and `'linux'` belong to both vocabularies and map to themselves, so
|
|
108
|
+
* the two spellings cannot disagree.
|
|
109
|
+
*/
|
|
110
|
+
declare function platformTag(platform?: NodeJS.Platform | PlatformTag): PlatformTag;
|
|
74
111
|
/**
|
|
75
112
|
* The identifying parameter names, as a detached frozen list.
|
|
76
113
|
*
|
|
@@ -107,9 +144,12 @@ declare function stampDeviceId(digest: string, source?: DeviceIdSource): string;
|
|
|
107
144
|
declare function fingerprintDeviceId(material: string, source?: DeviceIdSource): string;
|
|
108
145
|
/**
|
|
109
146
|
* Split a stamped device id into its parts, or `null` if it is not a Moonbase
|
|
110
|
-
* stamp (a legacy id, or one from a custom resolver). Lets a validator tell
|
|
147
|
+
* stamp at all (a legacy id, or one from a custom resolver). Lets a validator tell
|
|
111
148
|
* "this license belongs to another machine" apart from "this license was bound
|
|
112
149
|
* by an older fingerprint version".
|
|
150
|
+
*
|
|
151
|
+
* An id whose *tag* is unrecognised still parses, with `source` null: it came from
|
|
152
|
+
* a newer SDK, and reporting it as unparseable would be worse than saying so.
|
|
113
153
|
*/
|
|
114
154
|
declare function parseDeviceIdStamp(deviceId: string): DeviceIdStamp | null;
|
|
115
155
|
/** Extract `IOPlatformUUID` from `ioreg` output: hyphens stripped, uppercased (spec: macOS `ioPlatformUuid`). */
|
|
@@ -136,7 +176,7 @@ declare function selectMachineId(...sources: string[]): string;
|
|
|
136
176
|
*/
|
|
137
177
|
declare function parseSmbiosParams(smbiosData: Buffer): FingerprintParam[];
|
|
138
178
|
/** The real, platform-dispatching identity reader used by {@link MoonbaseDeviceIdResolver}. */
|
|
139
|
-
declare function defaultDeviceIdentityReader(platform?: NodeJS.Platform): DeviceIdentityReader;
|
|
179
|
+
declare function defaultDeviceIdentityReader(platform?: NodeJS.Platform | PlatformTag): DeviceIdentityReader;
|
|
140
180
|
|
|
141
181
|
interface IDeviceIdResolver {
|
|
142
182
|
resolveDeviceName: () => Promise<string>;
|
|
@@ -181,8 +221,16 @@ interface IMigratingDeviceIdResolver extends IDeviceIdResolver {
|
|
|
181
221
|
interface MoonbaseDeviceIdResolverOptions {
|
|
182
222
|
/** Overrides the identity source. Primarily for testing. */
|
|
183
223
|
reader?: DeviceIdentityReader;
|
|
184
|
-
/**
|
|
185
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Overrides the detected platform. Accepts a Node.js `process.platform` value
|
|
226
|
+
* or a canonical {@link PlatformTag}.
|
|
227
|
+
*
|
|
228
|
+
* The tag spelling exists for hosts that bridge a platform Node does not run
|
|
229
|
+
* on: pass `'ios'` together with a {@link reader} that supplies
|
|
230
|
+
* `identifierForVendor`. Without it the platform would resolve to `'unknown'`,
|
|
231
|
+
* which changes the material and so the device id.
|
|
232
|
+
*/
|
|
233
|
+
platform?: NodeJS.Platform | PlatformTag;
|
|
186
234
|
/**
|
|
187
235
|
* What to do when no hardware identity is readable. `'none'` (the default)
|
|
188
236
|
* throws {@link InsufficientDeviceIdentityError}; `'deviceName'` falls back to
|
|
@@ -190,6 +238,10 @@ interface MoonbaseDeviceIdResolverOptions {
|
|
|
190
238
|
*
|
|
191
239
|
* The fallback is opt-in because a host name is user-renameable, frequently
|
|
192
240
|
* duplicated across imaged machines, and regenerated on every container start.
|
|
241
|
+
*
|
|
242
|
+
* It is **ignored on iOS and Android**, which throw regardless: there the host
|
|
243
|
+
* name is identical on every device, so the fallback would give a whole install
|
|
244
|
+
* base one id rather than merely a weak one.
|
|
193
245
|
*/
|
|
194
246
|
fallback?: 'none' | 'deviceName';
|
|
195
247
|
}
|
|
@@ -545,13 +597,28 @@ declare class LicenseValidator implements ILicenseValidator {
|
|
|
545
597
|
private parseLicenseToken;
|
|
546
598
|
}
|
|
547
599
|
|
|
600
|
+
/**
|
|
601
|
+
* What went wrong, on a {@link MoonbaseError}. The license types say the license
|
|
602
|
+
* is no longer usable, so a stored copy can be deleted. `ApiError` only says no
|
|
603
|
+
* answer was had: keep the stored license and try again later.
|
|
604
|
+
*/
|
|
548
605
|
declare enum ErrorType {
|
|
549
606
|
None = "None",
|
|
607
|
+
/**
|
|
608
|
+
* The request failed without saying anything about the license: an outage, a
|
|
609
|
+
* rate limit, a proxy error page. Transient, so keep any stored license. A
|
|
610
|
+
* network failure is not a `MoonbaseError` at all, and is transient too.
|
|
611
|
+
*/
|
|
550
612
|
ApiError = "ApiError",
|
|
613
|
+
/** The customer has no license for this product to activate. */
|
|
551
614
|
NoEligibleLicense = "NoEligibleLicense",
|
|
615
|
+
/** The license token failed verification (signature, issuer or product), or the stored license could not be read. */
|
|
552
616
|
LicenseInvalid = "LicenseInvalid",
|
|
617
|
+
/** The merchant revoked the license, for example after a refund. */
|
|
553
618
|
LicenseRevoked = "LicenseRevoked",
|
|
619
|
+
/** This device's activation was revoked, freeing its seat. The license itself may still be active. */
|
|
554
620
|
LicenseActivationRevoked = "LicenseActivationRevoked",
|
|
621
|
+
/** The license, or trial, has expired. */
|
|
555
622
|
LicenseExpired = "LicenseExpired",
|
|
556
623
|
/** The license is valid but bound to a different device, or to an older fingerprint version. */
|
|
557
624
|
LicenseDeviceMismatch = "LicenseDeviceMismatch",
|
|
@@ -575,14 +642,15 @@ declare class MoonbaseError extends Error {
|
|
|
575
642
|
* either would hand a whole class of machines the *same* device id, and a license
|
|
576
643
|
* bound to it would validate on all of them.
|
|
577
644
|
*
|
|
578
|
-
* Reachable on platforms with no
|
|
579
|
-
* anything unknown
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
645
|
+
* Reachable on platforms with no identity parameters this package can read (BSD
|
|
646
|
+
* and anything unknown, which the spec leaves undefined, plus Android, whose
|
|
647
|
+
* `androidId` needs Android-framework API a Node.js process cannot call); when
|
|
648
|
+
* every source fails — a sandboxed process that cannot spawn `ioreg`, a container
|
|
649
|
+
* with no DMI, a blocked PowerShell; and on machines whose per-device identifiers
|
|
650
|
+
* are simply absent, such as a Linux install with no `machine-id` or a VM whose
|
|
651
|
+
* SMBIOS carries an unset UUID and a blank baseboard serial. Enable the host-name
|
|
652
|
+
* fallback (`new MoonbaseDeviceIdResolver({ fallback: 'deviceName' })`) to accept
|
|
653
|
+
* a deliberately weaker id on those machines.
|
|
586
654
|
*/
|
|
587
655
|
declare class InsufficientDeviceIdentityError extends MoonbaseError {
|
|
588
656
|
readonly platform: string;
|
|
@@ -631,4 +699,4 @@ declare class MoonbaseLicensing {
|
|
|
631
699
|
readRawLicense(license: Buffer): Promise<License>;
|
|
632
700
|
}
|
|
633
701
|
|
|
634
|
-
export { ActivationMethod, type ActivationRequestResponse, type DeviceIdDescription, type DeviceIdSource, type DeviceIdStamp, type DeviceIdentity, type DeviceIdentityReader, type DeviceToken, ErrorType, FINGERPRINT_PREFIX, FINGERPRINT_VERSION, FileLicenseStore, type FingerprintParam, IDENTIFYING_PARAM_NAMES, type IDescribableDeviceIdResolver, type IDeviceIdResolver, type ILicenseClient, type ILicenseStore, type ILicenseValidator, type IMigratingDeviceIdResolver, InMemoryLicenseStore, InsufficientDeviceIdentityError, LegacyDeviceIdResolver, type License, LicenseClient, LicenseValidator, MAX_VALUE_LENGTH, type Metadata, MigratingDeviceIdResolver, type MoonbaseConfiguration, MoonbaseDeviceIdResolver, type MoonbaseDeviceIdResolverOptions, MoonbaseError, MoonbaseLicensing, type Platform, type PlatformTag, type Product, type User, buildFingerprintMaterial, canonicalizeParams, canonicalizeValue, defaultDeviceIdentityReader, fingerprintDeviceId, fingerprintDigest, parseDeviceIdStamp, parseIoregPlatformUuid, parseSmbiosParams, platformTag, selectMachineId, stampDeviceId };
|
|
702
|
+
export { ActivationMethod, type ActivationRequestResponse, DEVICE_ID_SOURCE_TAGS, type DeviceIdDescription, type DeviceIdSource, type DeviceIdStamp, type DeviceIdentity, type DeviceIdentityReader, type DeviceToken, ErrorType, FINGERPRINT_PREFIX, FINGERPRINT_VERSION, FileLicenseStore, type FingerprintParam, IDENTIFYING_PARAM_NAMES, type IDescribableDeviceIdResolver, type IDeviceIdResolver, type ILicenseClient, type ILicenseStore, type ILicenseValidator, type IMigratingDeviceIdResolver, InMemoryLicenseStore, InsufficientDeviceIdentityError, LegacyDeviceIdResolver, type License, LicenseClient, LicenseValidator, MAX_VALUE_LENGTH, type Metadata, MigratingDeviceIdResolver, type MoonbaseConfiguration, MoonbaseDeviceIdResolver, type MoonbaseDeviceIdResolverOptions, MoonbaseError, MoonbaseLicensing, type Platform, type PlatformTag, type Product, type User, buildFingerprintMaterial, canonicalizeParams, canonicalizeValue, defaultDeviceIdentityReader, fingerprintDeviceId, fingerprintDigest, identitySource, parseDeviceIdStamp, parseIoregPlatformUuid, parseSmbiosParams, platformTag, selectMachineId, stampDeviceId };
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,42 @@ import process3 from "process";
|
|
|
5
5
|
// src/client.ts
|
|
6
6
|
import fetch from "cross-fetch";
|
|
7
7
|
|
|
8
|
+
// src/errors.ts
|
|
9
|
+
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
10
|
+
ErrorType2["None"] = "None";
|
|
11
|
+
ErrorType2["ApiError"] = "ApiError";
|
|
12
|
+
ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
|
|
13
|
+
ErrorType2["LicenseInvalid"] = "LicenseInvalid";
|
|
14
|
+
ErrorType2["LicenseRevoked"] = "LicenseRevoked";
|
|
15
|
+
ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
|
|
16
|
+
ErrorType2["LicenseExpired"] = "LicenseExpired";
|
|
17
|
+
ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
|
|
18
|
+
ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
|
|
19
|
+
return ErrorType2;
|
|
20
|
+
})(ErrorType || {});
|
|
21
|
+
var MoonbaseError = class extends Error {
|
|
22
|
+
constructor(title, detail, type, inner) {
|
|
23
|
+
super();
|
|
24
|
+
this.title = title;
|
|
25
|
+
this.detail = detail;
|
|
26
|
+
this.type = type;
|
|
27
|
+
this.inner = inner;
|
|
28
|
+
this.name = "MoonbaseError";
|
|
29
|
+
this.message = detail != null ? detail : title;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var InsufficientDeviceIdentityError = class extends MoonbaseError {
|
|
33
|
+
constructor(platform, reason = "no identity parameter could be read") {
|
|
34
|
+
super(
|
|
35
|
+
"No device identity",
|
|
36
|
+
`Could not identify this device (platform: ${platform}): ${reason}`,
|
|
37
|
+
"DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
|
|
38
|
+
);
|
|
39
|
+
this.platform = platform;
|
|
40
|
+
this.name = "InsufficientDeviceIdentityError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
8
44
|
// src/schemas.ts
|
|
9
45
|
import { z } from "zod";
|
|
10
46
|
|
|
@@ -21,6 +57,11 @@ var activationRequestResponseSchema = z.object({
|
|
|
21
57
|
request: z.string(),
|
|
22
58
|
browser: z.string()
|
|
23
59
|
});
|
|
60
|
+
var problemDetailsSchema = z.object({
|
|
61
|
+
title: z.string().nullish(),
|
|
62
|
+
detail: z.string().nullish(),
|
|
63
|
+
errorType: z.string().nullish()
|
|
64
|
+
});
|
|
24
65
|
var productSchema = z.object({
|
|
25
66
|
id: z.string(),
|
|
26
67
|
name: z.string(),
|
|
@@ -75,7 +116,7 @@ var LicenseClient = class {
|
|
|
75
116
|
body: JSON.stringify(content)
|
|
76
117
|
});
|
|
77
118
|
if (response.status >= 400) {
|
|
78
|
-
throw
|
|
119
|
+
throw await errorFromResponse(response);
|
|
79
120
|
}
|
|
80
121
|
try {
|
|
81
122
|
return activationRequestResponseSchema.parse(await response.json());
|
|
@@ -140,12 +181,12 @@ var LicenseClient = class {
|
|
|
140
181
|
body: license.token
|
|
141
182
|
});
|
|
142
183
|
if (response.status >= 400) {
|
|
143
|
-
throw
|
|
184
|
+
throw await errorFromResponse(response);
|
|
144
185
|
}
|
|
145
186
|
}
|
|
146
187
|
async handleLicenseResponse(response) {
|
|
147
188
|
if (response.status >= 400) {
|
|
148
|
-
throw
|
|
189
|
+
throw await errorFromResponse(response);
|
|
149
190
|
}
|
|
150
191
|
return await this.licenseValidator.validateLicense(await response.text());
|
|
151
192
|
}
|
|
@@ -166,6 +207,35 @@ var LicenseClient = class {
|
|
|
166
207
|
return `?${parts.join("&")}`;
|
|
167
208
|
}
|
|
168
209
|
};
|
|
210
|
+
var licenseErrorTypes = /* @__PURE__ */ new Set([
|
|
211
|
+
"NoEligibleLicense" /* NoEligibleLicense */,
|
|
212
|
+
"LicenseInvalid" /* LicenseInvalid */,
|
|
213
|
+
"LicenseRevoked" /* LicenseRevoked */,
|
|
214
|
+
"LicenseActivationRevoked" /* LicenseActivationRevoked */,
|
|
215
|
+
"LicenseExpired" /* LicenseExpired */
|
|
216
|
+
]);
|
|
217
|
+
async function errorFromResponse(response) {
|
|
218
|
+
const fallback = new MoonbaseError(
|
|
219
|
+
"Request not successful",
|
|
220
|
+
`The API responded with a ${response.status} ${response.statusText} response`,
|
|
221
|
+
"ApiError" /* ApiError */
|
|
222
|
+
);
|
|
223
|
+
let body;
|
|
224
|
+
try {
|
|
225
|
+
body = JSON.parse(await response.text());
|
|
226
|
+
} catch (e) {
|
|
227
|
+
return fallback;
|
|
228
|
+
}
|
|
229
|
+
const problem = problemDetailsSchema.safeParse(body);
|
|
230
|
+
if (!problem.success)
|
|
231
|
+
return fallback;
|
|
232
|
+
const { title, detail, errorType } = problem.data;
|
|
233
|
+
return new MoonbaseError(
|
|
234
|
+
title != null ? title : fallback.title,
|
|
235
|
+
detail != null ? detail : fallback.detail,
|
|
236
|
+
errorType && licenseErrorTypes.has(errorType) ? errorType : "ApiError" /* ApiError */
|
|
237
|
+
);
|
|
238
|
+
}
|
|
169
239
|
|
|
170
240
|
// src/deviceIdResolver.ts
|
|
171
241
|
import cp2 from "child_process";
|
|
@@ -174,42 +244,6 @@ import os2 from "os";
|
|
|
174
244
|
import process2 from "process";
|
|
175
245
|
import si from "systeminformation";
|
|
176
246
|
|
|
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
247
|
// src/fingerprint.ts
|
|
214
248
|
import { Buffer } from "buffer";
|
|
215
249
|
import cp from "child_process";
|
|
@@ -222,7 +256,20 @@ var FINGERPRINT_VERSION = 2;
|
|
|
222
256
|
var MAX_VALUE_LENGTH = 128;
|
|
223
257
|
var PRINTABLE_ASCII_MIN = 32;
|
|
224
258
|
var PRINTABLE_ASCII_MAX = 126;
|
|
225
|
-
var
|
|
259
|
+
var SOURCE_TAGS = {
|
|
260
|
+
identity: "",
|
|
261
|
+
deviceName: "n",
|
|
262
|
+
scoped: "s"
|
|
263
|
+
};
|
|
264
|
+
var DEVICE_ID_SOURCE_TAGS = Object.freeze({ ...SOURCE_TAGS });
|
|
265
|
+
var SOURCE_BY_TAG = new Map(
|
|
266
|
+
Object.entries(SOURCE_TAGS).map(([source, tag]) => [tag, source])
|
|
267
|
+
);
|
|
268
|
+
var SCOPED_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
269
|
+
function identitySource(platform) {
|
|
270
|
+
return SCOPED_PLATFORMS.has(platform) ? "scoped" : "identity";
|
|
271
|
+
}
|
|
272
|
+
var STAMP_PATTERN = /^mbd(\d+)([a-z]*)_([0-9a-f]{64})$/;
|
|
226
273
|
function canonicalizeValue(value) {
|
|
227
274
|
var _a;
|
|
228
275
|
let printable = "";
|
|
@@ -233,6 +280,12 @@ function canonicalizeValue(value) {
|
|
|
233
280
|
}
|
|
234
281
|
return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
|
|
235
282
|
}
|
|
283
|
+
var PLATFORM_TAGS = /* @__PURE__ */ new Set(
|
|
284
|
+
["mac", "ios", "windows", "android", "linux", "bsd", "unknown"]
|
|
285
|
+
);
|
|
286
|
+
function isPlatformTag(value) {
|
|
287
|
+
return PLATFORM_TAGS.has(value);
|
|
288
|
+
}
|
|
236
289
|
function platformTag(platform = process.platform) {
|
|
237
290
|
switch (platform) {
|
|
238
291
|
case "darwin":
|
|
@@ -248,7 +301,7 @@ function platformTag(platform = process.platform) {
|
|
|
248
301
|
case "netbsd":
|
|
249
302
|
return "bsd";
|
|
250
303
|
default:
|
|
251
|
-
return "unknown";
|
|
304
|
+
return isPlatformTag(platform) ? platform : "unknown";
|
|
252
305
|
}
|
|
253
306
|
}
|
|
254
307
|
var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
@@ -256,6 +309,8 @@ var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
|
|
|
256
309
|
"machineId",
|
|
257
310
|
"systemUuid",
|
|
258
311
|
"baseboardSerialNumber",
|
|
312
|
+
"identifierForVendor",
|
|
313
|
+
"androidId",
|
|
259
314
|
"deviceName"
|
|
260
315
|
]);
|
|
261
316
|
var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
|
|
@@ -280,9 +335,35 @@ var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
|
|
|
280
335
|
// deployed from such an image reads it, so it is the opposite of an identifier.
|
|
281
336
|
"uninitialized"
|
|
282
337
|
]);
|
|
338
|
+
var IDENTITY_CONSTRAINTS = {
|
|
339
|
+
androidId: {
|
|
340
|
+
// Reading the *static field* `Settings.Secure.ANDROID_ID` instead of calling
|
|
341
|
+
// `getString` with it yields the key name "android_id", identical on every
|
|
342
|
+
// device, so a single activation would unlock a whole Android install base.
|
|
343
|
+
// That string is not hex, so this stops it reaching the material.
|
|
344
|
+
//
|
|
345
|
+
// The bound is 1,16 and not 16 because AOSP before 8.0 generated the value
|
|
346
|
+
// with `Long.toHexString`, which drops leading zeros — a strict 16 would
|
|
347
|
+
// reject legitimate ids on roughly one in sixteen pre-Oreo devices.
|
|
348
|
+
format: /^[0-9a-f]{1,16}$/,
|
|
349
|
+
// A real ANDROID_ID shared by a large batch of 2010-era devices whose
|
|
350
|
+
// `ro.serialno` was unset, seeding the generator identically on every unit.
|
|
351
|
+
// Valid hex, so the format rule cannot catch it.
|
|
352
|
+
rejected: /* @__PURE__ */ new Set(["9774d56d682e549c"])
|
|
353
|
+
}
|
|
354
|
+
};
|
|
283
355
|
function isNotProgrammed(value) {
|
|
284
356
|
return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
|
|
285
357
|
}
|
|
358
|
+
function isUsableIdentity(name, value) {
|
|
359
|
+
var _a, _b, _c;
|
|
360
|
+
if (isNotProgrammed(value))
|
|
361
|
+
return false;
|
|
362
|
+
const constraint = IDENTITY_CONSTRAINTS[name];
|
|
363
|
+
if (!constraint)
|
|
364
|
+
return true;
|
|
365
|
+
return ((_b = (_a = constraint.format) == null ? void 0 : _a.test(value)) != null ? _b : true) && !((_c = constraint.rejected) == null ? void 0 : _c.has(value.toLowerCase()));
|
|
366
|
+
}
|
|
286
367
|
function canonicalizeParams(params) {
|
|
287
368
|
const kept = [];
|
|
288
369
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -290,7 +371,7 @@ function canonicalizeParams(params) {
|
|
|
290
371
|
const value = canonicalizeValue(rawValue);
|
|
291
372
|
if (value.length === 0)
|
|
292
373
|
continue;
|
|
293
|
-
if (IDENTIFYING_PARAMS.has(name) &&
|
|
374
|
+
if (IDENTIFYING_PARAMS.has(name) && !isUsableIdentity(name, value))
|
|
294
375
|
continue;
|
|
295
376
|
if (seen.has(name))
|
|
296
377
|
throw new Error(`Duplicate fingerprint parameter name: ${name}`);
|
|
@@ -303,6 +384,12 @@ function buildFingerprintMaterial(platform, params) {
|
|
|
303
384
|
const kept = canonicalizeParams(params);
|
|
304
385
|
if (kept.length === 0)
|
|
305
386
|
throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
|
|
387
|
+
if (SCOPED_PLATFORMS.has(platform) && kept.some(([name]) => name === "deviceName")) {
|
|
388
|
+
throw new InsufficientDeviceIdentityError(
|
|
389
|
+
platform,
|
|
390
|
+
"the host-name fallback is not available on this platform, where the host name is the same on every device"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
306
393
|
if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
|
|
307
394
|
throw new InsufficientDeviceIdentityError(
|
|
308
395
|
platform,
|
|
@@ -318,18 +405,20 @@ function fingerprintDigest(material) {
|
|
|
318
405
|
return createHash("sha256").update(material, "utf8").digest("hex");
|
|
319
406
|
}
|
|
320
407
|
function stampDeviceId(digest, source = "identity") {
|
|
321
|
-
return `mbd${FINGERPRINT_VERSION}${source
|
|
408
|
+
return `mbd${FINGERPRINT_VERSION}${SOURCE_TAGS[source]}_${digest}`;
|
|
322
409
|
}
|
|
323
410
|
function fingerprintDeviceId(material, source = "identity") {
|
|
324
411
|
return stampDeviceId(fingerprintDigest(material), source);
|
|
325
412
|
}
|
|
326
413
|
function parseDeviceIdStamp(deviceId) {
|
|
414
|
+
var _a;
|
|
327
415
|
const match = STAMP_PATTERN.exec(deviceId);
|
|
328
416
|
if (!match)
|
|
329
417
|
return null;
|
|
330
418
|
return {
|
|
331
419
|
version: Number(match[1]),
|
|
332
|
-
|
|
420
|
+
sourceTag: match[2],
|
|
421
|
+
source: (_a = SOURCE_BY_TAG.get(match[2])) != null ? _a : null,
|
|
333
422
|
digest: match[3]
|
|
334
423
|
};
|
|
335
424
|
}
|
|
@@ -458,18 +547,23 @@ function readWindowsIdentity() {
|
|
|
458
547
|
return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: os.hostname() };
|
|
459
548
|
}
|
|
460
549
|
function defaultDeviceIdentityReader(platform = process.platform) {
|
|
550
|
+
const tag = platformTag(platform);
|
|
461
551
|
return {
|
|
462
552
|
read() {
|
|
463
|
-
switch (
|
|
464
|
-
case "
|
|
553
|
+
switch (tag) {
|
|
554
|
+
case "mac":
|
|
465
555
|
return readMacIdentity();
|
|
466
556
|
case "linux":
|
|
467
557
|
return readLinuxIdentity();
|
|
468
|
-
case "
|
|
558
|
+
case "windows":
|
|
469
559
|
return readWindowsIdentity();
|
|
470
|
-
//
|
|
471
|
-
// has no stable hardware source for them, so they resolve to an
|
|
560
|
+
// Everything else yields no identity params here, so it resolves to an
|
|
472
561
|
// insufficient-identity error unless the deviceName fallback is enabled.
|
|
562
|
+
// For bsd and unknown that matches the spec, which defines none. Android
|
|
563
|
+
// is different: the spec *does* define `androidId`, but it comes from
|
|
564
|
+
// `Settings.Secure.getString`, which is Android-framework API that a
|
|
565
|
+
// Node.js process cannot reach. Collecting it is the C++/.NET SDKs' job.
|
|
566
|
+
// A host that can bridge it should supply its own DeviceIdentityReader.
|
|
473
567
|
default:
|
|
474
568
|
return { params: [], deviceName: os.hostname() };
|
|
475
569
|
}
|
|
@@ -521,7 +615,7 @@ var MoonbaseDeviceIdResolver = class {
|
|
|
521
615
|
computeDescription() {
|
|
522
616
|
const { params, deviceName } = this.readIdentity();
|
|
523
617
|
try {
|
|
524
|
-
return this.describe(params,
|
|
618
|
+
return this.describe(params, identitySource(this.platform));
|
|
525
619
|
} catch (err) {
|
|
526
620
|
if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
|
|
527
621
|
throw err;
|
|
@@ -768,25 +862,48 @@ var LicenseValidator = class {
|
|
|
768
862
|
};
|
|
769
863
|
function deviceMismatchError(expected, bound) {
|
|
770
864
|
const detail = "This license is not for this device";
|
|
771
|
-
const
|
|
865
|
+
const stampNote = describeStampDifference(expected, bound);
|
|
772
866
|
return new MoonbaseError(
|
|
773
867
|
"License is for another device",
|
|
774
|
-
|
|
868
|
+
stampNote ? `${detail}. ${stampNote}` : detail,
|
|
775
869
|
"LicenseDeviceMismatch" /* LicenseDeviceMismatch */
|
|
776
870
|
);
|
|
777
871
|
}
|
|
778
|
-
function
|
|
872
|
+
function describeStampDifference(expected, bound) {
|
|
779
873
|
const expectedStamp = parseDeviceIdStamp(expected);
|
|
780
874
|
if (!expectedStamp)
|
|
781
875
|
return null;
|
|
782
876
|
const boundStamp = parseDeviceIdStamp(bound);
|
|
783
|
-
if (boundStamp
|
|
784
|
-
return
|
|
785
|
-
if (boundStamp
|
|
786
|
-
return
|
|
877
|
+
if (!boundStamp || boundStamp.version !== expectedStamp.version)
|
|
878
|
+
return describeVersionDifference(expectedStamp, boundStamp);
|
|
879
|
+
if (boundStamp.sourceTag !== expectedStamp.sourceTag)
|
|
880
|
+
return describeSourceDifference(expectedStamp, boundStamp);
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
function describeVersionDifference(expected, bound) {
|
|
884
|
+
if (bound && bound.version > expected.version) {
|
|
885
|
+
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.`;
|
|
886
|
+
}
|
|
887
|
+
const boundVersion = bound ? `device fingerprint v${bound.version}` : "an SDK predating versioned device fingerprints";
|
|
888
|
+
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.`;
|
|
889
|
+
}
|
|
890
|
+
function describeSourceDifference(expected, bound) {
|
|
891
|
+
if (bound.source === "scoped") {
|
|
892
|
+
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.";
|
|
893
|
+
}
|
|
894
|
+
if (expected.source === "scoped") {
|
|
895
|
+
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.";
|
|
896
|
+
}
|
|
897
|
+
if (bound.source === null) {
|
|
898
|
+
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.`;
|
|
899
|
+
}
|
|
900
|
+
if (expected.source === null) {
|
|
901
|
+
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.`;
|
|
902
|
+
}
|
|
903
|
+
if (bound.source === "deviceName") {
|
|
904
|
+
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.";
|
|
787
905
|
}
|
|
788
|
-
|
|
789
|
-
return `The binding was created by ${boundVersion}, while this SDK computes v${expectedStamp.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.`;
|
|
906
|
+
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.";
|
|
790
907
|
}
|
|
791
908
|
|
|
792
909
|
// src/index.ts
|
|
@@ -833,6 +950,7 @@ var MoonbaseLicensing = class {
|
|
|
833
950
|
};
|
|
834
951
|
export {
|
|
835
952
|
ActivationMethod,
|
|
953
|
+
DEVICE_ID_SOURCE_TAGS,
|
|
836
954
|
ErrorType,
|
|
837
955
|
FINGERPRINT_PREFIX,
|
|
838
956
|
FINGERPRINT_VERSION,
|
|
@@ -854,6 +972,7 @@ export {
|
|
|
854
972
|
defaultDeviceIdentityReader,
|
|
855
973
|
fingerprintDeviceId,
|
|
856
974
|
fingerprintDigest,
|
|
975
|
+
identitySource,
|
|
857
976
|
parseDeviceIdStamp,
|
|
858
977
|
parseIoregPlatformUuid,
|
|
859
978
|
parseSmbiosParams,
|