slybrowser 0.2.1 → 0.3.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/README.md +8 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -26
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +0 -19
- package/dist/errors.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +54 -8
- package/dist/installer.js.map +1 -1
- package/dist/licensed.d.ts.map +1 -1
- package/dist/licensed.js +0 -3
- package/dist/licensed.js.map +1 -1
- package/dist/service.d.ts +2 -13
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +81 -294
- package/dist/service.js.map +1 -1
- package/package.json +77 -77
package/dist/service.d.ts
CHANGED
|
@@ -9,19 +9,10 @@ export interface LicenseAuthorization {
|
|
|
9
9
|
}
|
|
10
10
|
export interface LicenseFileReadOptions {
|
|
11
11
|
allowInsecureLocalhost?: boolean;
|
|
12
|
-
licenseFilePassphrase?: string;
|
|
13
12
|
licenseFileTrustedKeys?: ReadonlyMap<string, Uint8Array> | Record<string, Uint8Array>;
|
|
14
|
-
licenseFileResolutionServiceUrl?: string;
|
|
15
13
|
trustedServiceUrls?: readonly string[];
|
|
16
14
|
fetch?: typeof fetch;
|
|
17
15
|
}
|
|
18
|
-
export interface SealedLicenseImportResult {
|
|
19
|
-
output: string;
|
|
20
|
-
serviceUrl: string;
|
|
21
|
-
channel: "stable";
|
|
22
|
-
protection: "windows-dpapi-current-user";
|
|
23
|
-
licenseKeySha256: string;
|
|
24
|
-
}
|
|
25
16
|
export interface LicensedSessionGrant {
|
|
26
17
|
sessionId: string;
|
|
27
18
|
sessionToken: string;
|
|
@@ -146,12 +137,10 @@ export interface LicenseServiceClientOptions {
|
|
|
146
137
|
licenseTrustedKeys: ReadonlyMap<string, Uint8Array> | Record<string, Uint8Array>;
|
|
147
138
|
releaseTrustedKeys: ReadonlyMap<string, Uint8Array> | Record<string, Uint8Array>;
|
|
148
139
|
licenseFileTrustedKeys?: ReadonlyMap<string, Uint8Array> | Record<string, Uint8Array>;
|
|
149
|
-
licenseFilePassphrase?: string;
|
|
150
140
|
trustedServiceUrls?: readonly string[];
|
|
151
141
|
fetch?: typeof fetch;
|
|
152
142
|
allowInsecureLocalhost?: boolean;
|
|
153
143
|
}
|
|
154
|
-
export declare function importLicenseFileToSealedAuthorization(inputPath: string, outputPath: string, options?: LicenseFileReadOptions): Promise<SealedLicenseImportResult>;
|
|
155
144
|
export declare function readLicenseAuthorization(path: string, options?: LicenseFileReadOptions): Promise<LicenseAuthorization>;
|
|
156
145
|
export declare class LicenseServiceClient {
|
|
157
146
|
#private;
|
|
@@ -200,7 +189,7 @@ export declare class LicenseServiceClient {
|
|
|
200
189
|
closeRuntimeSession(grant: RuntimeActivationGrant): Promise<RuntimeHeartbeatGrant>;
|
|
201
190
|
release(grant: Pick<LicensedSessionGrant, "sessionId" | "sessionToken">): Promise<void>;
|
|
202
191
|
releaseRuntimeSession(grant: RuntimeSessionGrant | RuntimeActivationGrant): Promise<void>;
|
|
203
|
-
downloadArtifact(grant: LicensedSessionGrant): Promise<Response>;
|
|
204
|
-
downloadRuntimeArtifact(grant: RuntimeSessionGrant): Promise<Response>;
|
|
192
|
+
downloadArtifact(grant: LicensedSessionGrant, signal?: AbortSignal): Promise<Response>;
|
|
193
|
+
downloadRuntimeArtifact(grant: RuntimeSessionGrant, signal?: AbortSignal): Promise<Response>;
|
|
205
194
|
}
|
|
206
195
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/service.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { createPublicKey, randomUUID, verify as verifySignature } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { resolve } from "node:path";
|
|
5
4
|
import { gunzipSync } from "node:zlib";
|
|
6
5
|
import { canonicalJson, decodeBase64Url } from "./canonical.js";
|
|
7
6
|
import { LicenseServiceError } from "./errors.js";
|
|
8
7
|
import { LicenseVerifier } from "./license.js";
|
|
9
8
|
import { isSdkCompatible, selectArtifact, verifyReleaseManifest, } from "./manifest.js";
|
|
9
|
+
const CDN_ARTIFACT_ORIGIN = "https://downloads.slybrowser.com";
|
|
10
|
+
const CDN_ARTIFACT_PREFIX = "/github.com/virtualfanc/slybrowser/releases/download/";
|
|
11
|
+
function isCdnArtifactUrl(urlText, artifact, browserVersion) {
|
|
12
|
+
const target = artifact.platform === "windows" ? "win" : artifact.platform === "macos" ? "mac" : "linux";
|
|
13
|
+
const filename = `slybrowser-${browserVersion}-${target}-${artifact.arch}.${artifact.archiveFormat}`;
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(urlText);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (url.origin !== CDN_ARTIFACT_ORIGIN || url.username || url.password || url.search || url.hash ||
|
|
22
|
+
!url.pathname.startsWith(CDN_ARTIFACT_PREFIX))
|
|
23
|
+
return false;
|
|
24
|
+
const remainder = url.pathname.slice(CDN_ARTIFACT_PREFIX.length);
|
|
25
|
+
const tag = remainder.slice(0, remainder.indexOf("/"));
|
|
26
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(tag) &&
|
|
27
|
+
remainder === `${tag}/${filename}` &&
|
|
28
|
+
urlText === `${CDN_ARTIFACT_ORIGIN}${url.pathname}`;
|
|
29
|
+
}
|
|
10
30
|
function fail(code, message, status = 0, details = {}) {
|
|
11
31
|
throw new LicenseServiceError(message, code, status, details);
|
|
12
32
|
}
|
|
@@ -107,21 +127,6 @@ const LICENSE_FILE_TOP_LEVEL_FIELDS = new Set([
|
|
|
107
127
|
"tag",
|
|
108
128
|
"signature",
|
|
109
129
|
]);
|
|
110
|
-
const LICENSE_FILE_SECRET_FIELDS = new Set([
|
|
111
|
-
"schemaVersion",
|
|
112
|
-
"type",
|
|
113
|
-
"audience",
|
|
114
|
-
"licenseId",
|
|
115
|
-
"fileId",
|
|
116
|
-
"serviceUrl",
|
|
117
|
-
"channel",
|
|
118
|
-
"licenseKey",
|
|
119
|
-
"secretVersion",
|
|
120
|
-
"createdAt",
|
|
121
|
-
"expiresAt",
|
|
122
|
-
"nonce",
|
|
123
|
-
"scope",
|
|
124
|
-
]);
|
|
125
130
|
const USER_LICENSE_FILE_FORMAT = "slybrowser-license-compact-v1";
|
|
126
131
|
const USER_LICENSE_FILE_FIELDS = new Set(["schemaVersion", "type", "format", "license"]);
|
|
127
132
|
const USER_LICENSE_FILE_MAX_COMPRESSED_BYTES = 64 * 1024;
|
|
@@ -214,7 +219,7 @@ function trustedKeyMap(keys) {
|
|
|
214
219
|
const entries = keys instanceof Map ? keys.entries() : Object.entries(keys ?? {});
|
|
215
220
|
return new Map([...entries].map(([key, value]) => [key, Buffer.from(value)]));
|
|
216
221
|
}
|
|
217
|
-
function
|
|
222
|
+
function validateEncryptedLicenseFile(value, options) {
|
|
218
223
|
const document = requiredObject(unwrapUserLicenseFile(value));
|
|
219
224
|
if (Object.keys(document).some((name) => !LICENSE_FILE_TOP_LEVEL_FIELDS.has(name)) ||
|
|
220
225
|
Object.keys(document).length !== LICENSE_FILE_TOP_LEVEL_FIELDS.size ||
|
|
@@ -266,70 +271,16 @@ function parseEncryptedLicenseFile(value, options) {
|
|
|
266
271
|
const verified = verifySignature(null, signedBody, createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, publicKey]), format: "der", type: "spki" }), decodeBase64Url(requiredText(signature, "signature", 256), 64));
|
|
267
272
|
if (!verified)
|
|
268
273
|
fail("license_file_signature_invalid", "License file signature is invalid", 401);
|
|
269
|
-
|
|
270
|
-
if (typeof passphrase !== "string" || passphrase.length < 12) {
|
|
271
|
-
fail("license_file_locked", "License file passphrase is missing or too short", 401);
|
|
272
|
-
}
|
|
273
|
-
const salt = decodeBase64Url(requiredText(kdf, "salt", 64), 16);
|
|
274
|
-
if (salt.length !== 16)
|
|
275
|
-
fail("authorization_invalid", "License file salt length is invalid");
|
|
276
|
-
const nonce = decodeBase64Url(requiredText(encryption, "nonce", 64), 12);
|
|
277
|
-
if (nonce.length !== 12)
|
|
278
|
-
fail("authorization_invalid", "License file nonce length is invalid");
|
|
279
|
-
const decipher = createDecipheriv("aes-256-gcm", scryptSync(passphrase, salt, 32, { N: 16_384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }), nonce);
|
|
280
|
-
decipher.setAAD(canonicalJson(publicLicenseFileHeader({ ...document, serviceUrl })));
|
|
281
|
-
decipher.setAuthTag(decodeBase64Url(requiredText(document, "tag", 64), 16));
|
|
282
|
-
let payload;
|
|
283
|
-
try {
|
|
284
|
-
const plaintext = Buffer.concat([
|
|
285
|
-
decipher.update(decodeBase64Url(requiredText(document, "ciphertext", 64 * 1024), 64 * 1024)),
|
|
286
|
-
decipher.final(),
|
|
287
|
-
]);
|
|
288
|
-
payload = requiredObject(JSON.parse(plaintext.toString("utf8")));
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
if (error instanceof LicenseServiceError)
|
|
292
|
-
throw error;
|
|
293
|
-
fail("license_file_locked", "License file cannot be decrypted", 401);
|
|
294
|
-
}
|
|
295
|
-
if (Object.keys(payload).some((name) => !LICENSE_FILE_SECRET_FIELDS.has(name)) ||
|
|
296
|
-
Object.keys(payload).length !== LICENSE_FILE_SECRET_FIELDS.size) {
|
|
297
|
-
fail("license_file_payload_invalid", "License file payload contains unsupported claims");
|
|
298
|
-
}
|
|
299
|
-
const licenseKey = requiredText(payload, "licenseKey", 256);
|
|
300
|
-
if (payload.schemaVersion !== 2 ||
|
|
301
|
-
payload.type !== "slybrowser-license-secret" ||
|
|
302
|
-
payload.audience !== document.audience ||
|
|
303
|
-
payload.licenseId !== licenseId ||
|
|
304
|
-
payload.fileId !== document.fileId ||
|
|
305
|
-
normalizeServiceUrl(requiredText(payload, "serviceUrl"), options.allowInsecureLocalhost ?? false) !== serviceUrl ||
|
|
306
|
-
payload.channel !== "stable" ||
|
|
307
|
-
payload.secretVersion !== 1 ||
|
|
308
|
-
payload.expiresAt !== document.expiresAt ||
|
|
309
|
-
payload.scope !== expectedScope ||
|
|
310
|
-
!/^sly_live_[0-9a-f-]{36}\.[A-Za-z0-9_-]{40,}$/.test(licenseKey) ||
|
|
311
|
-
!licenseKey.startsWith(`sly_live_${licenseId}.`)) {
|
|
312
|
-
fail("license_file_payload_invalid", "License file payload does not match its public header");
|
|
313
|
-
}
|
|
314
|
-
return { schemaVersion: 1, serviceUrl, licenseKey, channel: "stable" };
|
|
315
|
-
}
|
|
316
|
-
function localLicenseFileUnlockRequested(options) {
|
|
317
|
-
return options.licenseFilePassphrase !== undefined;
|
|
274
|
+
return { serviceUrl, licenseId };
|
|
318
275
|
}
|
|
319
276
|
async function resolveEncryptedLicenseFileViaService(value, options) {
|
|
320
|
-
const
|
|
321
|
-
if (document.schemaVersion !== 2 || document.type !== "slybrowser-license" ||
|
|
322
|
-
document.audience !== "slybrowser-license-file" || document.channel !== "stable") {
|
|
323
|
-
fail("authorization_invalid", "License file fields are invalid");
|
|
324
|
-
}
|
|
325
|
-
const documentServiceUrl = normalizeServiceUrl(requiredText(document, "serviceUrl"), options.allowInsecureLocalhost ?? false);
|
|
326
|
-
assertTrustedAuthorizationOrigin(documentServiceUrl, options);
|
|
327
|
-
const resolutionServiceUrl = normalizeServiceUrl(options.licenseFileResolutionServiceUrl ?? documentServiceUrl, options.allowInsecureLocalhost ?? false);
|
|
277
|
+
const { serviceUrl: documentServiceUrl, licenseId } = validateEncryptedLicenseFile(value, options);
|
|
328
278
|
const fetchImplementation = options.fetch ?? fetch;
|
|
329
279
|
let response;
|
|
330
280
|
try {
|
|
331
|
-
response = await fetchImplementation(`${
|
|
281
|
+
response = await fetchImplementation(`${documentServiceUrl}/v1/license-files/authorization`, {
|
|
332
282
|
method: "POST",
|
|
283
|
+
redirect: "error",
|
|
333
284
|
headers: {
|
|
334
285
|
"content-type": "application/json",
|
|
335
286
|
"user-agent": "SlyBrowserNodeSDK/license-file",
|
|
@@ -362,205 +313,12 @@ async function resolveEncryptedLicenseFileViaService(value, options) {
|
|
|
362
313
|
throw new LicenseServiceError(message, code, response.status, details);
|
|
363
314
|
}
|
|
364
315
|
const authorization = parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
365
|
-
if (authorization.serviceUrl !== documentServiceUrl
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return authorization;
|
|
369
|
-
}
|
|
370
|
-
const SEALED_LICENSE_TOP_LEVEL_FIELDS = new Set([
|
|
371
|
-
"schemaVersion",
|
|
372
|
-
"type",
|
|
373
|
-
"audience",
|
|
374
|
-
"serviceUrl",
|
|
375
|
-
"channel",
|
|
376
|
-
"sealedAt",
|
|
377
|
-
"licenseKeySha256",
|
|
378
|
-
"protection",
|
|
379
|
-
"ciphertext",
|
|
380
|
-
]);
|
|
381
|
-
function licenseKeySha256(licenseKey) {
|
|
382
|
-
return createHash("sha256").update(licenseKey, "utf8").digest("hex");
|
|
383
|
-
}
|
|
384
|
-
function sealedLicenseEntropy(document) {
|
|
385
|
-
return [
|
|
386
|
-
"slybrowser-sealed-license-v1",
|
|
387
|
-
requiredText(document, "serviceUrl", 2048),
|
|
388
|
-
requiredText(document, "channel", 16),
|
|
389
|
-
requiredText(document, "licenseKeySha256", 64),
|
|
390
|
-
].join("\0");
|
|
391
|
-
}
|
|
392
|
-
function assertTrustedAuthorizationOrigin(serviceUrl, options) {
|
|
393
|
-
const trustedServiceUrls = (options.trustedServiceUrls ?? ["https://api.slybrowser.com"])
|
|
394
|
-
.map((url) => normalizeServiceUrl(url, options.allowInsecureLocalhost ?? false));
|
|
395
|
-
if (!trustedServiceUrls.includes(serviceUrl)) {
|
|
396
|
-
fail("license_file_untrusted_origin", "License file service URL is not trusted", 403);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
async function windowsDpapi(operation, data, entropy) {
|
|
400
|
-
if (process.platform !== "win32") {
|
|
401
|
-
fail("sealed_license_unsupported", "Windows DPAPI sealed license files are only supported on Windows", 400);
|
|
402
|
-
}
|
|
403
|
-
const script = `
|
|
404
|
-
$ErrorActionPreference = 'Stop'
|
|
405
|
-
[System.Reflection.Assembly]::LoadWithPartialName('System.Security') | Out-Null
|
|
406
|
-
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
407
|
-
$data = [Convert]::FromBase64String([string]$request.data)
|
|
408
|
-
$entropy = [Convert]::FromBase64String([string]$request.entropy)
|
|
409
|
-
$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser
|
|
410
|
-
if ([string]$request.operation -eq 'protect') {
|
|
411
|
-
$output = [System.Security.Cryptography.ProtectedData]::Protect($data, $entropy, $scope)
|
|
412
|
-
} elseif ([string]$request.operation -eq 'unprotect') {
|
|
413
|
-
$output = [System.Security.Cryptography.ProtectedData]::Unprotect($data, $entropy, $scope)
|
|
414
|
-
} else {
|
|
415
|
-
throw 'Unsupported DPAPI operation'
|
|
416
|
-
}
|
|
417
|
-
[Console]::Out.Write([Convert]::ToBase64String($output))
|
|
418
|
-
`;
|
|
419
|
-
const encodedCommand = Buffer.from(script, "utf16le").toString("base64");
|
|
420
|
-
const request = JSON.stringify({
|
|
421
|
-
operation,
|
|
422
|
-
data: data.toString("base64"),
|
|
423
|
-
entropy: Buffer.from(entropy, "utf8").toString("base64"),
|
|
424
|
-
});
|
|
425
|
-
return await new Promise((resolvePromise, reject) => {
|
|
426
|
-
const child = spawn("powershell.exe", [
|
|
427
|
-
"-NoLogo",
|
|
428
|
-
"-NoProfile",
|
|
429
|
-
"-NonInteractive",
|
|
430
|
-
"-ExecutionPolicy",
|
|
431
|
-
"Bypass",
|
|
432
|
-
"-EncodedCommand",
|
|
433
|
-
encodedCommand,
|
|
434
|
-
], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
435
|
-
const stdout = [];
|
|
436
|
-
const stderr = [];
|
|
437
|
-
let settled = false;
|
|
438
|
-
const rejectOnce = (error) => {
|
|
439
|
-
if (settled)
|
|
440
|
-
return;
|
|
441
|
-
settled = true;
|
|
442
|
-
reject(error);
|
|
443
|
-
};
|
|
444
|
-
child.stdout.on("data", (chunk) => {
|
|
445
|
-
stdout.push(chunk);
|
|
446
|
-
if (Buffer.concat(stdout).length > 16 * 1024) {
|
|
447
|
-
child.kill();
|
|
448
|
-
rejectOnce(new LicenseServiceError("Windows DPAPI returned an oversized response", "sealed_license_invalid", 400));
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
child.stderr.on("data", (chunk) => {
|
|
452
|
-
stderr.push(chunk);
|
|
453
|
-
if (Buffer.concat(stderr).length > 8 * 1024)
|
|
454
|
-
child.kill();
|
|
455
|
-
});
|
|
456
|
-
child.on("error", () => {
|
|
457
|
-
rejectOnce(new LicenseServiceError("Windows DPAPI is unavailable", "sealed_license_unsupported", 400));
|
|
458
|
-
});
|
|
459
|
-
child.on("close", (code) => {
|
|
460
|
-
if (settled)
|
|
461
|
-
return;
|
|
462
|
-
settled = true;
|
|
463
|
-
if (code !== 0) {
|
|
464
|
-
reject(new LicenseServiceError("Sealed license file cannot be decrypted on this Windows user or machine", "sealed_license_locked", 401));
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
try {
|
|
468
|
-
resolvePromise(Buffer.from(Buffer.concat(stdout).toString("utf8").trim(), "base64"));
|
|
469
|
-
}
|
|
470
|
-
catch {
|
|
471
|
-
reject(new LicenseServiceError("Windows DPAPI returned an invalid sealed license response", "sealed_license_invalid", 400));
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
child.stdin.end(request);
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
async function parseSealedLicenseFile(value, options) {
|
|
478
|
-
const document = requiredObject(value);
|
|
479
|
-
if (Object.keys(document).some((name) => !SEALED_LICENSE_TOP_LEVEL_FIELDS.has(name)) ||
|
|
480
|
-
Object.keys(document).length !== SEALED_LICENSE_TOP_LEVEL_FIELDS.size ||
|
|
481
|
-
document.schemaVersion !== 3 ||
|
|
482
|
-
document.type !== "slybrowser-sealed-authorization" ||
|
|
483
|
-
document.audience !== "slybrowser") {
|
|
484
|
-
fail("sealed_license_invalid", "Sealed license file fields are invalid");
|
|
485
|
-
}
|
|
486
|
-
const serviceUrl = normalizeServiceUrl(requiredText(document, "serviceUrl"), options.allowInsecureLocalhost ?? false);
|
|
487
|
-
assertTrustedAuthorizationOrigin(serviceUrl, options);
|
|
488
|
-
if (requiredText(document, "channel", 16) !== "stable")
|
|
489
|
-
fail("sealed_license_invalid", "Sealed license channel is invalid");
|
|
490
|
-
const sealedAt = Date.parse(requiredText(document, "sealedAt", 64));
|
|
491
|
-
if (!Number.isFinite(sealedAt))
|
|
492
|
-
fail("sealed_license_invalid", "Sealed license timestamp is invalid");
|
|
493
|
-
const sha256 = requiredText(document, "licenseKeySha256", 64);
|
|
494
|
-
if (!/^[0-9a-f]{64}$/.test(sha256))
|
|
495
|
-
fail("sealed_license_invalid", "Sealed license fingerprint is invalid");
|
|
496
|
-
const protection = requiredObject(document.protection);
|
|
497
|
-
if (Object.keys(protection).sort().join(",") !== "provider,scope" ||
|
|
498
|
-
protection.provider !== "windows-dpapi" ||
|
|
499
|
-
protection.scope !== "current-user") {
|
|
500
|
-
fail("sealed_license_unsupported", "Sealed license protection is not supported", 400);
|
|
501
|
-
}
|
|
502
|
-
const ciphertext = decodeBase64Url(requiredText(document, "ciphertext", 64 * 1024), 64 * 1024);
|
|
503
|
-
let parsed;
|
|
504
|
-
try {
|
|
505
|
-
const plaintext = await windowsDpapi("unprotect", ciphertext, sealedLicenseEntropy({ ...document, serviceUrl }));
|
|
506
|
-
parsed = JSON.parse(plaintext.toString("utf8"));
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
if (error instanceof LicenseServiceError)
|
|
510
|
-
throw error;
|
|
511
|
-
fail("sealed_license_locked", "Sealed license file cannot be decrypted on this Windows user or machine", 401);
|
|
512
|
-
}
|
|
513
|
-
const authorization = parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
514
|
-
if (authorization.serviceUrl !== serviceUrl || authorization.channel !== "stable" ||
|
|
515
|
-
licenseKeySha256(authorization.licenseKey) !== sha256) {
|
|
516
|
-
fail("sealed_license_mismatch", "Sealed license payload does not match its public header", 400);
|
|
316
|
+
if (authorization.serviceUrl !== documentServiceUrl ||
|
|
317
|
+
!authorization.licenseKey.startsWith(`sly_live_${licenseId}.`)) {
|
|
318
|
+
fail("license_service_invalid_response", "License-file authorization does not match the signed file", response.status);
|
|
517
319
|
}
|
|
518
320
|
return authorization;
|
|
519
321
|
}
|
|
520
|
-
export async function importLicenseFileToSealedAuthorization(inputPath, outputPath, options = {}) {
|
|
521
|
-
const raw = await readFile(resolve(inputPath));
|
|
522
|
-
if (raw.length > 64 * 1024)
|
|
523
|
-
fail("authorization_invalid", "Authorization file is too large");
|
|
524
|
-
let parsed;
|
|
525
|
-
try {
|
|
526
|
-
parsed = JSON.parse(raw.toString("utf8"));
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
fail("authorization_invalid", "Authorization file is not valid JSON");
|
|
530
|
-
}
|
|
531
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schemaVersion !== 2) {
|
|
532
|
-
fail("license_file_invalid", "Only encrypted v2 SlyBrowser license files can be imported", 400);
|
|
533
|
-
}
|
|
534
|
-
const authorization = parseEncryptedLicenseFile(parsed, options);
|
|
535
|
-
assertTrustedAuthorizationOrigin(authorization.serviceUrl, options);
|
|
536
|
-
const fingerprint = licenseKeySha256(authorization.licenseKey);
|
|
537
|
-
const sealedAt = new Date().toISOString();
|
|
538
|
-
const document = {
|
|
539
|
-
schemaVersion: 3,
|
|
540
|
-
type: "slybrowser-sealed-authorization",
|
|
541
|
-
audience: "slybrowser",
|
|
542
|
-
serviceUrl: authorization.serviceUrl,
|
|
543
|
-
channel: authorization.channel,
|
|
544
|
-
sealedAt,
|
|
545
|
-
licenseKeySha256: fingerprint,
|
|
546
|
-
protection: {
|
|
547
|
-
provider: "windows-dpapi",
|
|
548
|
-
scope: "current-user",
|
|
549
|
-
},
|
|
550
|
-
ciphertext: "",
|
|
551
|
-
};
|
|
552
|
-
const ciphertext = await windowsDpapi("protect", Buffer.from(JSON.stringify(authorization), "utf8"), sealedLicenseEntropy(document));
|
|
553
|
-
document.ciphertext = ciphertext.toString("base64url");
|
|
554
|
-
const output = resolve(outputPath);
|
|
555
|
-
await writeFile(output, `${JSON.stringify(document, null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
556
|
-
return {
|
|
557
|
-
output,
|
|
558
|
-
serviceUrl: authorization.serviceUrl,
|
|
559
|
-
channel: "stable",
|
|
560
|
-
protection: "windows-dpapi-current-user",
|
|
561
|
-
licenseKeySha256: fingerprint,
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
322
|
export async function readLicenseAuthorization(path, options = {}) {
|
|
565
323
|
const raw = await readFile(resolve(path));
|
|
566
324
|
if (raw.length > 64 * 1024)
|
|
@@ -578,14 +336,7 @@ export async function readLicenseAuthorization(path, options = {}) {
|
|
|
578
336
|
: parsed;
|
|
579
337
|
if (document && typeof document === "object" && !Array.isArray(document) &&
|
|
580
338
|
document.schemaVersion === 2) {
|
|
581
|
-
|
|
582
|
-
return await resolveEncryptedLicenseFileViaService(parsed, options);
|
|
583
|
-
}
|
|
584
|
-
return parseEncryptedLicenseFile(parsed, options);
|
|
585
|
-
}
|
|
586
|
-
if (document && typeof document === "object" && !Array.isArray(document) &&
|
|
587
|
-
document.schemaVersion === 3) {
|
|
588
|
-
return await parseSealedLicenseFile(parsed, options);
|
|
339
|
+
return await resolveEncryptedLicenseFileViaService(parsed, options);
|
|
589
340
|
}
|
|
590
341
|
return parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
591
342
|
}
|
|
@@ -708,7 +459,7 @@ export class LicenseServiceClient {
|
|
|
708
459
|
async licenseInfo(options = {}) {
|
|
709
460
|
const platform = options.platform ?? currentPlatform();
|
|
710
461
|
const arch = options.arch ?? currentArch();
|
|
711
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
462
|
+
const sdkVersion = options.sdkVersion ?? "0.3.1";
|
|
712
463
|
const versionPolicy = options.versionPolicy ?? (options.browserVersion === undefined ? "latest" : "exact");
|
|
713
464
|
if (versionPolicy === "latest" && options.browserVersion !== undefined) {
|
|
714
465
|
fail("version_policy_invalid", "Latest selection cannot include a requested browser version");
|
|
@@ -814,7 +565,7 @@ export class LicenseServiceClient {
|
|
|
814
565
|
async createSession(options = {}) {
|
|
815
566
|
const platform = options.platform ?? currentPlatform();
|
|
816
567
|
const arch = options.arch ?? currentArch();
|
|
817
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
568
|
+
const sdkVersion = options.sdkVersion ?? "0.3.1";
|
|
818
569
|
const versionPolicy = options.versionPolicy ?? (options.browserVersion === undefined ? "latest" : "exact");
|
|
819
570
|
if (versionPolicy === "latest" && options.browserVersion !== undefined) {
|
|
820
571
|
fail("version_policy_invalid", "Latest selection cannot include a requested browser version");
|
|
@@ -899,8 +650,8 @@ export class LicenseServiceClient {
|
|
|
899
650
|
}
|
|
900
651
|
const artifact = selectArtifact(manifest, platform, arch);
|
|
901
652
|
const serviceOrigin = new URL(this.authorization.serviceUrl).origin;
|
|
902
|
-
if (new URL(artifact.url).origin !== serviceOrigin) {
|
|
903
|
-
fail("artifact_origin_invalid", "Authorized
|
|
653
|
+
if (new URL(artifact.url).origin !== serviceOrigin && !isCdnArtifactUrl(artifact.url, artifact, browserVersion)) {
|
|
654
|
+
fail("artifact_origin_invalid", "Authorized artifact origin or CDN path is invalid");
|
|
904
655
|
}
|
|
905
656
|
const plan = value.plan;
|
|
906
657
|
if (!new Set(["free", "basic", "pro", "max", "ultra"]).has(String(plan)) ||
|
|
@@ -940,7 +691,7 @@ export class LicenseServiceClient {
|
|
|
940
691
|
async createRuntimeSession(options = {}) {
|
|
941
692
|
const platform = options.platform ?? currentPlatform();
|
|
942
693
|
const arch = options.arch ?? currentArch();
|
|
943
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
694
|
+
const sdkVersion = options.sdkVersion ?? "0.3.1";
|
|
944
695
|
const startupId = options.startupId ?? newStartupId();
|
|
945
696
|
if (!/^st_[A-Za-z0-9_-]{16,120}$/.test(startupId)) {
|
|
946
697
|
fail("startup_id_invalid", "Runtime startup ID is invalid");
|
|
@@ -1043,8 +794,8 @@ export class LicenseServiceClient {
|
|
|
1043
794
|
}
|
|
1044
795
|
const artifact = selectArtifact(manifest, platform, arch);
|
|
1045
796
|
const serviceOrigin = new URL(this.authorization.serviceUrl).origin;
|
|
1046
|
-
if (new URL(artifact.url).origin !== serviceOrigin) {
|
|
1047
|
-
fail("artifact_origin_invalid", "Authorized
|
|
797
|
+
if (new URL(artifact.url).origin !== serviceOrigin && !isCdnArtifactUrl(artifact.url, artifact, browserVersion)) {
|
|
798
|
+
fail("artifact_origin_invalid", "Authorized artifact origin or CDN path is invalid");
|
|
1048
799
|
}
|
|
1049
800
|
const ticket = value.downloadTicket;
|
|
1050
801
|
const artifactUrl = String(ticket?.artifactUrl ?? "");
|
|
@@ -1052,7 +803,8 @@ export class LicenseServiceClient {
|
|
|
1052
803
|
if (!ticket || typeof ticket.token !== "string" || !ticket.token ||
|
|
1053
804
|
!Number.isSafeInteger(ticket.expiresAt) || ticket.expiresAt !== value.expiresAt ||
|
|
1054
805
|
artifactSha256 !== artifact.sha256 || !artifactUrl ||
|
|
1055
|
-
new URL(artifactUrl).origin !== serviceOrigin
|
|
806
|
+
(new URL(artifactUrl).origin !== serviceOrigin &&
|
|
807
|
+
!(artifactUrl === artifact.url && isCdnArtifactUrl(artifact.url, artifact, browserVersion)))) {
|
|
1056
808
|
fail("license_service_invalid_response", "Runtime download ticket response is invalid");
|
|
1057
809
|
}
|
|
1058
810
|
const plan = value.plan;
|
|
@@ -1170,18 +922,52 @@ export class LicenseServiceClient {
|
|
|
1170
922
|
authorization: `Bootstrap ${grant.bootstrapToken}`,
|
|
1171
923
|
});
|
|
1172
924
|
}
|
|
1173
|
-
async downloadArtifact(grant) {
|
|
1174
|
-
|
|
925
|
+
async downloadArtifact(grant, signal) {
|
|
926
|
+
let artifactUrl;
|
|
927
|
+
try {
|
|
928
|
+
artifactUrl = new URL(grant.artifact.url);
|
|
929
|
+
}
|
|
930
|
+
catch {
|
|
931
|
+
fail("license_service_invalid_response", "Artifact URL is invalid");
|
|
932
|
+
}
|
|
933
|
+
const cdn = isCdnArtifactUrl(grant.artifact.url, grant.artifact, grant.browserVersion);
|
|
934
|
+
if (!cdn && artifactUrl.origin !== new URL(this.authorization.serviceUrl).origin) {
|
|
935
|
+
fail("artifact_origin_invalid", "Authorized artifact origin or CDN path is invalid");
|
|
936
|
+
}
|
|
937
|
+
const response = await this.#fetch(artifactUrl.toString(), {
|
|
1175
938
|
method: "GET",
|
|
1176
|
-
headers: { authorization: `Session ${grant.sessionToken}` },
|
|
939
|
+
headers: cdn ? {} : { authorization: `Session ${grant.sessionToken}` },
|
|
1177
940
|
redirect: "error",
|
|
941
|
+
...(signal === undefined ? {} : { signal }),
|
|
1178
942
|
});
|
|
1179
943
|
if (!response.ok || !response.body)
|
|
1180
944
|
await this.#throwResponse(response);
|
|
1181
945
|
return response;
|
|
1182
946
|
}
|
|
1183
|
-
async downloadRuntimeArtifact(grant) {
|
|
1184
|
-
|
|
947
|
+
async downloadRuntimeArtifact(grant, signal) {
|
|
948
|
+
let artifactUrl;
|
|
949
|
+
try {
|
|
950
|
+
artifactUrl = new URL(grant.downloadTicket.artifactUrl);
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
fail("license_service_invalid_response", "Runtime artifact URL is invalid");
|
|
954
|
+
}
|
|
955
|
+
const cdn = grant.downloadTicket.artifactUrl === grant.artifact.url &&
|
|
956
|
+
isCdnArtifactUrl(grant.artifact.url, grant.artifact, grant.browserVersion);
|
|
957
|
+
if (cdn) {
|
|
958
|
+
const response = await this.#fetch(artifactUrl.toString(), {
|
|
959
|
+
method: "GET",
|
|
960
|
+
headers: {},
|
|
961
|
+
redirect: "error",
|
|
962
|
+
...(signal === undefined ? {} : { signal }),
|
|
963
|
+
});
|
|
964
|
+
if (!response.ok || !response.body)
|
|
965
|
+
await this.#throwResponse(response);
|
|
966
|
+
return response;
|
|
967
|
+
}
|
|
968
|
+
if (artifactUrl.origin !== new URL(this.authorization.serviceUrl).origin) {
|
|
969
|
+
fail("artifact_origin_invalid", "Runtime artifact origin is invalid");
|
|
970
|
+
}
|
|
1185
971
|
if (artifactUrl.pathname.startsWith("/v1/releases/artifacts/")) {
|
|
1186
972
|
artifactUrl.pathname = artifactUrl.pathname.replace("/v1/releases/artifacts/", "/v2/runtime/artifacts/");
|
|
1187
973
|
}
|
|
@@ -1192,6 +978,7 @@ export class LicenseServiceClient {
|
|
|
1192
978
|
method: "GET",
|
|
1193
979
|
headers: { authorization: `Download ${grant.downloadTicket.token}` },
|
|
1194
980
|
redirect: "error",
|
|
981
|
+
...(signal === undefined ? {} : { signal }),
|
|
1195
982
|
});
|
|
1196
983
|
if (!response.ok || !response.body)
|
|
1197
984
|
await this.#throwResponse(response);
|