azot 0.8.0 → 0.8.3
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 +2 -1
- package/dist/cli/main.cjs +86 -42
- package/dist/lib/main.cjs +98 -51
- package/dist/lib/main.d.cts +13 -10
- package/dist/lib/main.d.ts +13 -10
- package/dist/lib/main.js +98 -51
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|

|
|
5
5
|
[](https://www.npmjs.com/package/azot)
|
|
6
6
|
|
|
7
|
-
Azot (Russian word for "nitrogen", pronounced `/azо́t/`) is a set of tools (JavaScript library, command-line utility and browser extension) for diagnosing, researching, and pentesting [
|
|
7
|
+
Azot (Russian word for "nitrogen", pronounced `/azо́t/`) is a set of tools (JavaScript library, command-line utility and browser extension) for diagnosing, researching, and pentesting [DRM](https://www.urbandictionary.com/define.php?term=DRM) systems like [Widevine](https://www.widevine.com/about).
|
|
8
8
|
|
|
9
9
|
> Azot is still in the early stages of development, so until version 1.0 is released, performance may be unstable and major changes may be made
|
|
10
10
|
|
|
@@ -18,6 +18,7 @@ Azot (Russian word for "nitrogen", pronounced `/azо́t/`) is a set of tools (Ja
|
|
|
18
18
|
- **Manifest V3** compliant browser extension
|
|
19
19
|
- **Custom client support**: WVD v2, device_client_id_blob + device_private_key, client_id.bin + private_key.pem
|
|
20
20
|
- **Network-independent interception**, so it doesn't matter if the request has one-time tokens or a custom request/response body format.
|
|
21
|
+
- **Remote instance** to control sessions via REST API
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
package/dist/cli/main.cjs
CHANGED
|
@@ -7202,6 +7202,7 @@ var Session = class extends EventTarget {
|
|
|
7202
7202
|
deriveContext(licenseRequest.bytes)
|
|
7203
7203
|
);
|
|
7204
7204
|
this.dispatchEvent(new MessageEvent("license-request", message.bytes));
|
|
7205
|
+
return message.bytes;
|
|
7205
7206
|
}
|
|
7206
7207
|
async waitForLicenseRequest() {
|
|
7207
7208
|
return new Promise((resolve) => {
|
|
@@ -7328,6 +7329,9 @@ var Session = class extends EventTarget {
|
|
|
7328
7329
|
"usable"
|
|
7329
7330
|
);
|
|
7330
7331
|
}
|
|
7332
|
+
async getKeys() {
|
|
7333
|
+
return Array.from(this.keys.values());
|
|
7334
|
+
}
|
|
7331
7335
|
close() {
|
|
7332
7336
|
this.dispatchEvent(new Event("closed"));
|
|
7333
7337
|
return Promise.resolve();
|
|
@@ -7476,18 +7480,34 @@ var Client = class _Client {
|
|
|
7476
7480
|
vmp;
|
|
7477
7481
|
info;
|
|
7478
7482
|
#key;
|
|
7479
|
-
static async fromPacked(data, format = "
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
+
static async fromPacked(data, format = "azot") {
|
|
7484
|
+
const isWvd = fromBuffer(data.slice(0, 3)).toText() == "WVD";
|
|
7485
|
+
if (format === "wvd" || isWvd) {
|
|
7486
|
+
const parsed = parseWvd(data);
|
|
7487
|
+
const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
|
|
7483
7488
|
${fromBuffer(parsed.privateKey).toBase64()}
|
|
7484
7489
|
-----END RSA PRIVATE KEY-----`;
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7490
|
+
const key = fromText(pcks1).toBuffer();
|
|
7491
|
+
const type = types.get(parsed.deviceType);
|
|
7492
|
+
const securityLevel = parsed.securityLevel;
|
|
7493
|
+
const client2 = new _Client(parsed.clientId, type, securityLevel);
|
|
7494
|
+
await client2.importKey(key);
|
|
7495
|
+
return client2;
|
|
7496
|
+
} else if (format === "azot") {
|
|
7497
|
+
const parsed = JSON.parse(fromBuffer(data).toText());
|
|
7498
|
+
const clientId = ClientIdentification.fromObject(
|
|
7499
|
+
parsed.data.clientIdentification
|
|
7500
|
+
);
|
|
7501
|
+
const client2 = new _Client(
|
|
7502
|
+
clientId,
|
|
7503
|
+
parsed.data.type,
|
|
7504
|
+
parsed.data.securityLevel
|
|
7505
|
+
);
|
|
7506
|
+
await client2.importKey(parsed.data.privateKey);
|
|
7507
|
+
return client2;
|
|
7508
|
+
} else {
|
|
7509
|
+
throw new Error("Unsupported format");
|
|
7510
|
+
}
|
|
7491
7511
|
}
|
|
7492
7512
|
static async fromUnpacked(id, key, vmp) {
|
|
7493
7513
|
const client2 = new _Client(id);
|
|
@@ -7503,7 +7523,7 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7503
7523
|
return this.#key;
|
|
7504
7524
|
}
|
|
7505
7525
|
constructor(id, type = CLIENT_TYPE.android, securityLevel = 3) {
|
|
7506
|
-
this.id = ClientIdentification.decode(id);
|
|
7526
|
+
this.id = ArrayBuffer.isView(id) ? ClientIdentification.decode(id) : id;
|
|
7507
7527
|
this.signedDrmCertificate = SignedDrmCertificate.decode(this.id.token);
|
|
7508
7528
|
this.drmCertificate = DrmCertificate.decode(
|
|
7509
7529
|
this.signedDrmCertificate.drmCertificate
|
|
@@ -7520,23 +7540,39 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7520
7540
|
const key = await this.exportKey();
|
|
7521
7541
|
return [id, key];
|
|
7522
7542
|
}
|
|
7523
|
-
async pack(format = "
|
|
7524
|
-
if (format
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7543
|
+
async pack(format = "azot") {
|
|
7544
|
+
if (format === "wvd") {
|
|
7545
|
+
const id = ClientIdentification.encode(this.id).finish();
|
|
7546
|
+
const key = await this.exportKey();
|
|
7547
|
+
const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
|
|
7548
|
+
const keyDerBinary = fromBase64(keyDer).toBuffer();
|
|
7549
|
+
const [type] = types.entries().find(([, type2]) => type2 === this.type);
|
|
7550
|
+
const wvd = buildWvd({
|
|
7551
|
+
clientId: id,
|
|
7552
|
+
deviceType: type,
|
|
7553
|
+
securityLevel: this.securityLevel,
|
|
7554
|
+
privateKey: keyDerBinary
|
|
7555
|
+
});
|
|
7556
|
+
return wvd;
|
|
7557
|
+
} else if (format === "azot") {
|
|
7558
|
+
const clientIdentification = this.id.toJSON();
|
|
7559
|
+
const privateKey = await this.exportKey();
|
|
7560
|
+
const payload = {
|
|
7561
|
+
version: 1,
|
|
7562
|
+
type: "widevine-client",
|
|
7563
|
+
data: {
|
|
7564
|
+
clientIdentification,
|
|
7565
|
+
privateKey: fromBuffer(privateKey).toText()
|
|
7566
|
+
}
|
|
7567
|
+
};
|
|
7568
|
+
const data = JSON.stringify(payload, null, 2);
|
|
7569
|
+
return fromText(data).toBuffer();
|
|
7570
|
+
} else {
|
|
7571
|
+
throw new Error("Unsupported format");
|
|
7572
|
+
}
|
|
7537
7573
|
}
|
|
7538
7574
|
async importKey(pkcs1) {
|
|
7539
|
-
const pkcs1pem = fromBuffer(pkcs1).toText();
|
|
7575
|
+
const pkcs1pem = typeof pkcs1 === "string" ? pkcs1 : fromBuffer(pkcs1).toText();
|
|
7540
7576
|
const pkcs8pem = toPKCS8(pkcs1pem);
|
|
7541
7577
|
const pemContents = pkcs8pem.split("\n").slice(1, -2).join("\n");
|
|
7542
7578
|
const data = fromBase64(pemContents).toBuffer();
|
|
@@ -7690,9 +7726,11 @@ var importClient = async (input) => {
|
|
|
7690
7726
|
const entries = isDir ? await (0, import_promises.readdir)(input) : [];
|
|
7691
7727
|
const idFilename = entries.find((entry) => entry.includes("client_id"));
|
|
7692
7728
|
const keyFilename = entries.find((entry) => entry.includes("private_key"));
|
|
7693
|
-
const
|
|
7729
|
+
const packedFilename = entries.find(
|
|
7730
|
+
(entry) => entry.endsWith("wvd") || entry.endsWith("azot")
|
|
7731
|
+
);
|
|
7694
7732
|
const isUnpacked = !!(idFilename && keyFilename);
|
|
7695
|
-
const isPacked = !!
|
|
7733
|
+
const isPacked = !!packedFilename;
|
|
7696
7734
|
if (isUnpacked) {
|
|
7697
7735
|
const idPath = (0, import_node_path.join)(input, idFilename);
|
|
7698
7736
|
const keyPath = (0, import_node_path.join)(input, keyFilename);
|
|
@@ -7700,9 +7738,10 @@ var importClient = async (input) => {
|
|
|
7700
7738
|
const key = await (0, import_promises.readFile)(keyPath);
|
|
7701
7739
|
return Client.fromUnpacked(id, key);
|
|
7702
7740
|
} else if (isPacked) {
|
|
7703
|
-
const
|
|
7704
|
-
const
|
|
7705
|
-
|
|
7741
|
+
const packedPath = (0, import_node_path.join)(input, packedFilename);
|
|
7742
|
+
const packed = await (0, import_promises.readFile)(packedPath);
|
|
7743
|
+
const ext = (0, import_node_path.extname)(packedPath);
|
|
7744
|
+
return await Client.fromPacked(packed, ext);
|
|
7706
7745
|
} else {
|
|
7707
7746
|
console.log(`Unable to find client files in ${input}`);
|
|
7708
7747
|
process.exit(1);
|
|
@@ -7728,13 +7767,16 @@ var help = () => {
|
|
|
7728
7767
|
col(`info <input>`) + "print info about client that is on <input> path (*.wvd filepath or directory with id and private key)"
|
|
7729
7768
|
);
|
|
7730
7769
|
console.log(
|
|
7731
|
-
col(`pack <input> <output>`) + "pack client id and private key from <input> directory into single
|
|
7770
|
+
col(`pack <input> <output>`) + "pack client id and private key from <input> directory into single file with <output> path"
|
|
7732
7771
|
);
|
|
7733
7772
|
console.log(
|
|
7734
|
-
col(`unpack <input> <output>`) + "unpack
|
|
7773
|
+
col(`unpack <input> <output>`) + "unpack from <input> path to separate client id and private key placed in <output> directory"
|
|
7735
7774
|
);
|
|
7736
7775
|
console.log("");
|
|
7737
7776
|
console.log(`Flags:`);
|
|
7777
|
+
console.log(
|
|
7778
|
+
col(`-f, --format`) + "Specify format for pack command (wvd/azot)"
|
|
7779
|
+
);
|
|
7738
7780
|
console.log(col(`-h, --help`) + "Display this menu and exit");
|
|
7739
7781
|
};
|
|
7740
7782
|
|
|
@@ -7749,13 +7791,14 @@ var info = async (input) => {
|
|
|
7749
7791
|
// src/cli/commands/client/pack.ts
|
|
7750
7792
|
var import_promises2 = require("fs/promises");
|
|
7751
7793
|
var import_node_path2 = require("path");
|
|
7752
|
-
var pack = async (input = process.cwd(), output) => {
|
|
7794
|
+
var pack = async (input = process.cwd(), format, output) => {
|
|
7753
7795
|
const client2 = await importClient(input);
|
|
7754
|
-
const
|
|
7755
|
-
const
|
|
7756
|
-
const
|
|
7757
|
-
|
|
7758
|
-
|
|
7796
|
+
const ext = format || (output ? (0, import_node_path2.extname)(output) : "azot");
|
|
7797
|
+
const data = await client2.pack(ext);
|
|
7798
|
+
const filename = `${client2.info.get("company_name")}-${client2.info.get("model_name")}`.replaceAll(" ", "-").toLowerCase();
|
|
7799
|
+
const outputPath = output || (0, import_node_path2.join)(process.cwd(), `${filename}.${ext}`);
|
|
7800
|
+
await (0, import_promises2.writeFile)(outputPath, data);
|
|
7801
|
+
console.log(`Client packed: ${outputPath}`);
|
|
7759
7802
|
};
|
|
7760
7803
|
|
|
7761
7804
|
// src/cli/commands/client/unpack.ts
|
|
@@ -7820,8 +7863,8 @@ license.help = help2;
|
|
|
7820
7863
|
// package.json
|
|
7821
7864
|
var package_default = {
|
|
7822
7865
|
name: "azot",
|
|
7823
|
-
version: "0.8.
|
|
7824
|
-
description: "
|
|
7866
|
+
version: "0.8.3",
|
|
7867
|
+
description: "The ultimate toolkit for pentesting DRM systems",
|
|
7825
7868
|
type: "module",
|
|
7826
7869
|
files: [
|
|
7827
7870
|
"dist",
|
|
@@ -8200,6 +8243,7 @@ var args = (0, import_node_util.parseArgs)({
|
|
|
8200
8243
|
help: { type: "boolean", short: "h" },
|
|
8201
8244
|
version: { type: "boolean", short: "v" },
|
|
8202
8245
|
debug: { type: "boolean", short: "d" },
|
|
8246
|
+
format: { type: "boolean", short: "f" },
|
|
8203
8247
|
host: { type: "string" },
|
|
8204
8248
|
port: { type: "string" },
|
|
8205
8249
|
secret: { type: "string", short: "s" },
|
|
@@ -8255,7 +8299,7 @@ var help4 = () => {
|
|
|
8255
8299
|
const [input, output] = positionals;
|
|
8256
8300
|
switch (subcommand) {
|
|
8257
8301
|
case "pack":
|
|
8258
|
-
client.pack(input, output);
|
|
8302
|
+
client.pack(input, args.values.format, output);
|
|
8259
8303
|
break;
|
|
8260
8304
|
case "unpack":
|
|
8261
8305
|
client.unpack(input, output);
|
package/dist/lib/main.cjs
CHANGED
|
@@ -7236,6 +7236,7 @@ var Session = class extends EventTarget {
|
|
|
7236
7236
|
deriveContext(licenseRequest.bytes)
|
|
7237
7237
|
);
|
|
7238
7238
|
this.dispatchEvent(new MessageEvent("license-request", message.bytes));
|
|
7239
|
+
return message.bytes;
|
|
7239
7240
|
}
|
|
7240
7241
|
async waitForLicenseRequest() {
|
|
7241
7242
|
return new Promise((resolve) => {
|
|
@@ -7362,6 +7363,9 @@ var Session = class extends EventTarget {
|
|
|
7362
7363
|
"usable"
|
|
7363
7364
|
);
|
|
7364
7365
|
}
|
|
7366
|
+
async getKeys() {
|
|
7367
|
+
return Array.from(this.keys.values());
|
|
7368
|
+
}
|
|
7365
7369
|
close() {
|
|
7366
7370
|
this.dispatchEvent(new Event("closed"));
|
|
7367
7371
|
return Promise.resolve();
|
|
@@ -7408,36 +7412,47 @@ var connect = ({ baseUrl, secret }) => {
|
|
|
7408
7412
|
"content-type": "application/json"
|
|
7409
7413
|
};
|
|
7410
7414
|
const json = (data) => JSON.stringify(data);
|
|
7411
|
-
const
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7415
|
+
const http = {
|
|
7416
|
+
post: async (route, body) => {
|
|
7417
|
+
const response = await fetch(`${baseUrl}${route}`, {
|
|
7418
|
+
method: "POST",
|
|
7419
|
+
headers,
|
|
7420
|
+
body: json(body)
|
|
7421
|
+
});
|
|
7422
|
+
const contentLength = response.headers.get("content-length");
|
|
7423
|
+
if (contentLength === "0") return;
|
|
7424
|
+
return response.json();
|
|
7425
|
+
},
|
|
7426
|
+
get: async (route) => {
|
|
7427
|
+
const response = await fetch(`${baseUrl}${route}`, {
|
|
7428
|
+
method: "GET",
|
|
7429
|
+
headers
|
|
7430
|
+
});
|
|
7431
|
+
return response.json();
|
|
7432
|
+
},
|
|
7433
|
+
delete: async (route) => {
|
|
7434
|
+
await fetch(`${baseUrl}${route}`, { method: "DELETE", headers });
|
|
7435
|
+
}
|
|
7427
7436
|
};
|
|
7428
|
-
const createSession = async (client) => {
|
|
7429
|
-
const data = await post(`/session`, { client });
|
|
7437
|
+
const createSession = async (sessionType, client) => {
|
|
7438
|
+
const data = await http.post(`/session`, { client });
|
|
7430
7439
|
const sessionId = data.id;
|
|
7431
|
-
const generateRequest = async (
|
|
7432
|
-
const data2 = await post(`/session/${sessionId}/generate-request`, {
|
|
7440
|
+
const generateRequest = async (initDataType, initData) => {
|
|
7441
|
+
const data2 = await http.post(`/session/${sessionId}/generate-request`, {
|
|
7433
7442
|
initDataType,
|
|
7434
|
-
initData
|
|
7443
|
+
initData: fromBuffer(initData).toBase64()
|
|
7435
7444
|
});
|
|
7436
|
-
return data2.licenseRequest;
|
|
7445
|
+
return fromBase64(data2.licenseRequest).toBuffer();
|
|
7437
7446
|
};
|
|
7438
|
-
const update = async (response) =>
|
|
7439
|
-
|
|
7440
|
-
|
|
7447
|
+
const update = async (response) => {
|
|
7448
|
+
await http.post(`/session/${sessionId}/update`, {
|
|
7449
|
+
response: fromBuffer(response).toBase64()
|
|
7450
|
+
});
|
|
7451
|
+
};
|
|
7452
|
+
const getKeys = async () => http.get(`/session/${sessionId}/keys`);
|
|
7453
|
+
const close = async () => http.delete(`/session/${sessionId}/close`);
|
|
7454
|
+
const remove = async () => http.delete(`/session/${sessionId}/remove`);
|
|
7455
|
+
return { sessionId, generateRequest, update, getKeys, close, remove };
|
|
7441
7456
|
};
|
|
7442
7457
|
return { createSession };
|
|
7443
7458
|
};
|
|
@@ -7551,18 +7566,34 @@ var Client = class _Client {
|
|
|
7551
7566
|
vmp;
|
|
7552
7567
|
info;
|
|
7553
7568
|
#key;
|
|
7554
|
-
static async fromPacked(data, format = "
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7569
|
+
static async fromPacked(data, format = "azot") {
|
|
7570
|
+
const isWvd = fromBuffer(data.slice(0, 3)).toText() == "WVD";
|
|
7571
|
+
if (format === "wvd" || isWvd) {
|
|
7572
|
+
const parsed = parseWvd(data);
|
|
7573
|
+
const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
|
|
7558
7574
|
${fromBuffer(parsed.privateKey).toBase64()}
|
|
7559
7575
|
-----END RSA PRIVATE KEY-----`;
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7576
|
+
const key = fromText(pcks1).toBuffer();
|
|
7577
|
+
const type = types.get(parsed.deviceType);
|
|
7578
|
+
const securityLevel = parsed.securityLevel;
|
|
7579
|
+
const client = new _Client(parsed.clientId, type, securityLevel);
|
|
7580
|
+
await client.importKey(key);
|
|
7581
|
+
return client;
|
|
7582
|
+
} else if (format === "azot") {
|
|
7583
|
+
const parsed = JSON.parse(fromBuffer(data).toText());
|
|
7584
|
+
const clientId = ClientIdentification.fromObject(
|
|
7585
|
+
parsed.data.clientIdentification
|
|
7586
|
+
);
|
|
7587
|
+
const client = new _Client(
|
|
7588
|
+
clientId,
|
|
7589
|
+
parsed.data.type,
|
|
7590
|
+
parsed.data.securityLevel
|
|
7591
|
+
);
|
|
7592
|
+
await client.importKey(parsed.data.privateKey);
|
|
7593
|
+
return client;
|
|
7594
|
+
} else {
|
|
7595
|
+
throw new Error("Unsupported format");
|
|
7596
|
+
}
|
|
7566
7597
|
}
|
|
7567
7598
|
static async fromUnpacked(id, key, vmp) {
|
|
7568
7599
|
const client = new _Client(id);
|
|
@@ -7578,7 +7609,7 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7578
7609
|
return this.#key;
|
|
7579
7610
|
}
|
|
7580
7611
|
constructor(id, type = CLIENT_TYPE.android, securityLevel = 3) {
|
|
7581
|
-
this.id = ClientIdentification.decode(id);
|
|
7612
|
+
this.id = ArrayBuffer.isView(id) ? ClientIdentification.decode(id) : id;
|
|
7582
7613
|
this.signedDrmCertificate = SignedDrmCertificate.decode(this.id.token);
|
|
7583
7614
|
this.drmCertificate = DrmCertificate.decode(
|
|
7584
7615
|
this.signedDrmCertificate.drmCertificate
|
|
@@ -7595,23 +7626,39 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7595
7626
|
const key = await this.exportKey();
|
|
7596
7627
|
return [id, key];
|
|
7597
7628
|
}
|
|
7598
|
-
async pack(format = "
|
|
7599
|
-
if (format
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7629
|
+
async pack(format = "azot") {
|
|
7630
|
+
if (format === "wvd") {
|
|
7631
|
+
const id = ClientIdentification.encode(this.id).finish();
|
|
7632
|
+
const key = await this.exportKey();
|
|
7633
|
+
const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
|
|
7634
|
+
const keyDerBinary = fromBase64(keyDer).toBuffer();
|
|
7635
|
+
const [type] = types.entries().find(([, type2]) => type2 === this.type);
|
|
7636
|
+
const wvd = buildWvd({
|
|
7637
|
+
clientId: id,
|
|
7638
|
+
deviceType: type,
|
|
7639
|
+
securityLevel: this.securityLevel,
|
|
7640
|
+
privateKey: keyDerBinary
|
|
7641
|
+
});
|
|
7642
|
+
return wvd;
|
|
7643
|
+
} else if (format === "azot") {
|
|
7644
|
+
const clientIdentification = this.id.toJSON();
|
|
7645
|
+
const privateKey = await this.exportKey();
|
|
7646
|
+
const payload = {
|
|
7647
|
+
version: 1,
|
|
7648
|
+
type: "widevine-client",
|
|
7649
|
+
data: {
|
|
7650
|
+
clientIdentification,
|
|
7651
|
+
privateKey: fromBuffer(privateKey).toText()
|
|
7652
|
+
}
|
|
7653
|
+
};
|
|
7654
|
+
const data = JSON.stringify(payload, null, 2);
|
|
7655
|
+
return fromText(data).toBuffer();
|
|
7656
|
+
} else {
|
|
7657
|
+
throw new Error("Unsupported format");
|
|
7658
|
+
}
|
|
7612
7659
|
}
|
|
7613
7660
|
async importKey(pkcs1) {
|
|
7614
|
-
const pkcs1pem = fromBuffer(pkcs1).toText();
|
|
7661
|
+
const pkcs1pem = typeof pkcs1 === "string" ? pkcs1 : fromBuffer(pkcs1).toText();
|
|
7615
7662
|
const pkcs8pem = toPKCS8(pkcs1pem);
|
|
7616
7663
|
const pemContents = pkcs8pem.split("\n").slice(1, -2).join("\n");
|
|
7617
7664
|
const data = fromBase64(pemContents).toBuffer();
|
package/dist/lib/main.d.cts
CHANGED
|
@@ -2326,7 +2326,7 @@ type SessionType = keyof typeof SESSION_TYPES;
|
|
|
2326
2326
|
/**
|
|
2327
2327
|
* https://www.w3.org/TR/encrypted-media-2/#mediakeysession-interface
|
|
2328
2328
|
*/
|
|
2329
|
-
declare class Session extends EventTarget
|
|
2329
|
+
declare class Session extends EventTarget {
|
|
2330
2330
|
#private;
|
|
2331
2331
|
readonly sessionId: string;
|
|
2332
2332
|
readonly keyStatuses: MediaKeyStatusMap;
|
|
@@ -2340,10 +2340,11 @@ declare class Session extends EventTarget implements MediaKeySession {
|
|
|
2340
2340
|
privacyMode?: boolean;
|
|
2341
2341
|
constructor(sessionType: SessionType | undefined, client: Client);
|
|
2342
2342
|
setLogger(logger: Logger): void;
|
|
2343
|
-
generateRequest(initDataType: string, initData: BufferSource): Promise<
|
|
2343
|
+
generateRequest(initDataType: string, initData: BufferSource): Promise<Uint8Array | undefined>;
|
|
2344
2344
|
waitForLicenseRequest(): Promise<Uint8Array>;
|
|
2345
2345
|
load(): Promise<boolean>;
|
|
2346
2346
|
update(response: Uint8Array): Promise<void>;
|
|
2347
|
+
getKeys(): Promise<Key[]>;
|
|
2347
2348
|
close(): Promise<void>;
|
|
2348
2349
|
remove(): Promise<void>;
|
|
2349
2350
|
}
|
|
@@ -2364,16 +2365,16 @@ declare class Client {
|
|
|
2364
2365
|
systemId: number;
|
|
2365
2366
|
vmp: FileHashes | null;
|
|
2366
2367
|
info: Map<string, string>;
|
|
2367
|
-
static fromPacked(data: Uint8Array, format?: '
|
|
2368
|
+
static fromPacked(data: Uint8Array, format?: 'wvd' | 'azot'): Promise<Client>;
|
|
2368
2369
|
static fromUnpacked(id: Uint8Array, key: Uint8Array, vmp?: Uint8Array): Promise<Client>;
|
|
2369
2370
|
get key(): {
|
|
2370
2371
|
forDecrypt: CryptoKey;
|
|
2371
2372
|
forSign: CryptoKey;
|
|
2372
2373
|
};
|
|
2373
|
-
constructor(id: Uint8Array, type?: ClientType, securityLevel?: SecurityLevel);
|
|
2374
|
+
constructor(id: Uint8Array | ClientIdentification, type?: ClientType, securityLevel?: SecurityLevel);
|
|
2374
2375
|
unpack(): Promise<[id: Uint8Array, key: Uint8Array]>;
|
|
2375
|
-
pack(format?: '
|
|
2376
|
-
importKey(pkcs1: Uint8Array): Promise<{
|
|
2376
|
+
pack(format?: 'wvd' | 'azot'): Promise<Uint8Array>;
|
|
2377
|
+
importKey(pkcs1: Uint8Array | string): Promise<{
|
|
2377
2378
|
forDecrypt: CryptoKey;
|
|
2378
2379
|
forSign: CryptoKey;
|
|
2379
2380
|
}>;
|
|
@@ -2401,11 +2402,13 @@ type ConnectParams = {
|
|
|
2401
2402
|
secret: string;
|
|
2402
2403
|
};
|
|
2403
2404
|
declare const connect: ({ baseUrl, secret }: ConnectParams) => {
|
|
2404
|
-
createSession: (client: string) => Promise<{
|
|
2405
|
+
createSession: (sessionType: SessionType, client: string) => Promise<{
|
|
2405
2406
|
sessionId: any;
|
|
2406
|
-
generateRequest: (
|
|
2407
|
-
update: (response:
|
|
2408
|
-
|
|
2407
|
+
generateRequest: (initDataType: string, initData: Uint8Array) => Promise<Uint8Array>;
|
|
2408
|
+
update: (response: Uint8Array) => Promise<void>;
|
|
2409
|
+
getKeys: () => Promise<any>;
|
|
2410
|
+
close: () => Promise<void>;
|
|
2411
|
+
remove: () => Promise<void>;
|
|
2409
2412
|
}>;
|
|
2410
2413
|
};
|
|
2411
2414
|
|
package/dist/lib/main.d.ts
CHANGED
|
@@ -2326,7 +2326,7 @@ type SessionType = keyof typeof SESSION_TYPES;
|
|
|
2326
2326
|
/**
|
|
2327
2327
|
* https://www.w3.org/TR/encrypted-media-2/#mediakeysession-interface
|
|
2328
2328
|
*/
|
|
2329
|
-
declare class Session extends EventTarget
|
|
2329
|
+
declare class Session extends EventTarget {
|
|
2330
2330
|
#private;
|
|
2331
2331
|
readonly sessionId: string;
|
|
2332
2332
|
readonly keyStatuses: MediaKeyStatusMap;
|
|
@@ -2340,10 +2340,11 @@ declare class Session extends EventTarget implements MediaKeySession {
|
|
|
2340
2340
|
privacyMode?: boolean;
|
|
2341
2341
|
constructor(sessionType: SessionType | undefined, client: Client);
|
|
2342
2342
|
setLogger(logger: Logger): void;
|
|
2343
|
-
generateRequest(initDataType: string, initData: BufferSource): Promise<
|
|
2343
|
+
generateRequest(initDataType: string, initData: BufferSource): Promise<Uint8Array | undefined>;
|
|
2344
2344
|
waitForLicenseRequest(): Promise<Uint8Array>;
|
|
2345
2345
|
load(): Promise<boolean>;
|
|
2346
2346
|
update(response: Uint8Array): Promise<void>;
|
|
2347
|
+
getKeys(): Promise<Key[]>;
|
|
2347
2348
|
close(): Promise<void>;
|
|
2348
2349
|
remove(): Promise<void>;
|
|
2349
2350
|
}
|
|
@@ -2364,16 +2365,16 @@ declare class Client {
|
|
|
2364
2365
|
systemId: number;
|
|
2365
2366
|
vmp: FileHashes | null;
|
|
2366
2367
|
info: Map<string, string>;
|
|
2367
|
-
static fromPacked(data: Uint8Array, format?: '
|
|
2368
|
+
static fromPacked(data: Uint8Array, format?: 'wvd' | 'azot'): Promise<Client>;
|
|
2368
2369
|
static fromUnpacked(id: Uint8Array, key: Uint8Array, vmp?: Uint8Array): Promise<Client>;
|
|
2369
2370
|
get key(): {
|
|
2370
2371
|
forDecrypt: CryptoKey;
|
|
2371
2372
|
forSign: CryptoKey;
|
|
2372
2373
|
};
|
|
2373
|
-
constructor(id: Uint8Array, type?: ClientType, securityLevel?: SecurityLevel);
|
|
2374
|
+
constructor(id: Uint8Array | ClientIdentification, type?: ClientType, securityLevel?: SecurityLevel);
|
|
2374
2375
|
unpack(): Promise<[id: Uint8Array, key: Uint8Array]>;
|
|
2375
|
-
pack(format?: '
|
|
2376
|
-
importKey(pkcs1: Uint8Array): Promise<{
|
|
2376
|
+
pack(format?: 'wvd' | 'azot'): Promise<Uint8Array>;
|
|
2377
|
+
importKey(pkcs1: Uint8Array | string): Promise<{
|
|
2377
2378
|
forDecrypt: CryptoKey;
|
|
2378
2379
|
forSign: CryptoKey;
|
|
2379
2380
|
}>;
|
|
@@ -2401,11 +2402,13 @@ type ConnectParams = {
|
|
|
2401
2402
|
secret: string;
|
|
2402
2403
|
};
|
|
2403
2404
|
declare const connect: ({ baseUrl, secret }: ConnectParams) => {
|
|
2404
|
-
createSession: (client: string) => Promise<{
|
|
2405
|
+
createSession: (sessionType: SessionType, client: string) => Promise<{
|
|
2405
2406
|
sessionId: any;
|
|
2406
|
-
generateRequest: (
|
|
2407
|
-
update: (response:
|
|
2408
|
-
|
|
2407
|
+
generateRequest: (initDataType: string, initData: Uint8Array) => Promise<Uint8Array>;
|
|
2408
|
+
update: (response: Uint8Array) => Promise<void>;
|
|
2409
|
+
getKeys: () => Promise<any>;
|
|
2410
|
+
close: () => Promise<void>;
|
|
2411
|
+
remove: () => Promise<void>;
|
|
2409
2412
|
}>;
|
|
2410
2413
|
};
|
|
2411
2414
|
|
package/dist/lib/main.js
CHANGED
|
@@ -7190,6 +7190,7 @@ var Session = class extends EventTarget {
|
|
|
7190
7190
|
deriveContext(licenseRequest.bytes)
|
|
7191
7191
|
);
|
|
7192
7192
|
this.dispatchEvent(new MessageEvent("license-request", message.bytes));
|
|
7193
|
+
return message.bytes;
|
|
7193
7194
|
}
|
|
7194
7195
|
async waitForLicenseRequest() {
|
|
7195
7196
|
return new Promise((resolve) => {
|
|
@@ -7316,6 +7317,9 @@ var Session = class extends EventTarget {
|
|
|
7316
7317
|
"usable"
|
|
7317
7318
|
);
|
|
7318
7319
|
}
|
|
7320
|
+
async getKeys() {
|
|
7321
|
+
return Array.from(this.keys.values());
|
|
7322
|
+
}
|
|
7319
7323
|
close() {
|
|
7320
7324
|
this.dispatchEvent(new Event("closed"));
|
|
7321
7325
|
return Promise.resolve();
|
|
@@ -7362,36 +7366,47 @@ var connect = ({ baseUrl, secret }) => {
|
|
|
7362
7366
|
"content-type": "application/json"
|
|
7363
7367
|
};
|
|
7364
7368
|
const json = (data) => JSON.stringify(data);
|
|
7365
|
-
const
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7369
|
+
const http = {
|
|
7370
|
+
post: async (route, body) => {
|
|
7371
|
+
const response = await fetch(`${baseUrl}${route}`, {
|
|
7372
|
+
method: "POST",
|
|
7373
|
+
headers,
|
|
7374
|
+
body: json(body)
|
|
7375
|
+
});
|
|
7376
|
+
const contentLength = response.headers.get("content-length");
|
|
7377
|
+
if (contentLength === "0") return;
|
|
7378
|
+
return response.json();
|
|
7379
|
+
},
|
|
7380
|
+
get: async (route) => {
|
|
7381
|
+
const response = await fetch(`${baseUrl}${route}`, {
|
|
7382
|
+
method: "GET",
|
|
7383
|
+
headers
|
|
7384
|
+
});
|
|
7385
|
+
return response.json();
|
|
7386
|
+
},
|
|
7387
|
+
delete: async (route) => {
|
|
7388
|
+
await fetch(`${baseUrl}${route}`, { method: "DELETE", headers });
|
|
7389
|
+
}
|
|
7381
7390
|
};
|
|
7382
|
-
const createSession = async (client) => {
|
|
7383
|
-
const data = await post(`/session`, { client });
|
|
7391
|
+
const createSession = async (sessionType, client) => {
|
|
7392
|
+
const data = await http.post(`/session`, { client });
|
|
7384
7393
|
const sessionId = data.id;
|
|
7385
|
-
const generateRequest = async (
|
|
7386
|
-
const data2 = await post(`/session/${sessionId}/generate-request`, {
|
|
7394
|
+
const generateRequest = async (initDataType, initData) => {
|
|
7395
|
+
const data2 = await http.post(`/session/${sessionId}/generate-request`, {
|
|
7387
7396
|
initDataType,
|
|
7388
|
-
initData
|
|
7397
|
+
initData: fromBuffer(initData).toBase64()
|
|
7389
7398
|
});
|
|
7390
|
-
return data2.licenseRequest;
|
|
7399
|
+
return fromBase64(data2.licenseRequest).toBuffer();
|
|
7391
7400
|
};
|
|
7392
|
-
const update = async (response) =>
|
|
7393
|
-
|
|
7394
|
-
|
|
7401
|
+
const update = async (response) => {
|
|
7402
|
+
await http.post(`/session/${sessionId}/update`, {
|
|
7403
|
+
response: fromBuffer(response).toBase64()
|
|
7404
|
+
});
|
|
7405
|
+
};
|
|
7406
|
+
const getKeys = async () => http.get(`/session/${sessionId}/keys`);
|
|
7407
|
+
const close = async () => http.delete(`/session/${sessionId}/close`);
|
|
7408
|
+
const remove = async () => http.delete(`/session/${sessionId}/remove`);
|
|
7409
|
+
return { sessionId, generateRequest, update, getKeys, close, remove };
|
|
7395
7410
|
};
|
|
7396
7411
|
return { createSession };
|
|
7397
7412
|
};
|
|
@@ -7505,18 +7520,34 @@ var Client = class _Client {
|
|
|
7505
7520
|
vmp;
|
|
7506
7521
|
info;
|
|
7507
7522
|
#key;
|
|
7508
|
-
static async fromPacked(data, format = "
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7523
|
+
static async fromPacked(data, format = "azot") {
|
|
7524
|
+
const isWvd = fromBuffer(data.slice(0, 3)).toText() == "WVD";
|
|
7525
|
+
if (format === "wvd" || isWvd) {
|
|
7526
|
+
const parsed = parseWvd(data);
|
|
7527
|
+
const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
|
|
7512
7528
|
${fromBuffer(parsed.privateKey).toBase64()}
|
|
7513
7529
|
-----END RSA PRIVATE KEY-----`;
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7530
|
+
const key = fromText(pcks1).toBuffer();
|
|
7531
|
+
const type = types.get(parsed.deviceType);
|
|
7532
|
+
const securityLevel = parsed.securityLevel;
|
|
7533
|
+
const client = new _Client(parsed.clientId, type, securityLevel);
|
|
7534
|
+
await client.importKey(key);
|
|
7535
|
+
return client;
|
|
7536
|
+
} else if (format === "azot") {
|
|
7537
|
+
const parsed = JSON.parse(fromBuffer(data).toText());
|
|
7538
|
+
const clientId = ClientIdentification.fromObject(
|
|
7539
|
+
parsed.data.clientIdentification
|
|
7540
|
+
);
|
|
7541
|
+
const client = new _Client(
|
|
7542
|
+
clientId,
|
|
7543
|
+
parsed.data.type,
|
|
7544
|
+
parsed.data.securityLevel
|
|
7545
|
+
);
|
|
7546
|
+
await client.importKey(parsed.data.privateKey);
|
|
7547
|
+
return client;
|
|
7548
|
+
} else {
|
|
7549
|
+
throw new Error("Unsupported format");
|
|
7550
|
+
}
|
|
7520
7551
|
}
|
|
7521
7552
|
static async fromUnpacked(id, key, vmp) {
|
|
7522
7553
|
const client = new _Client(id);
|
|
@@ -7532,7 +7563,7 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7532
7563
|
return this.#key;
|
|
7533
7564
|
}
|
|
7534
7565
|
constructor(id, type = CLIENT_TYPE.android, securityLevel = 3) {
|
|
7535
|
-
this.id = ClientIdentification.decode(id);
|
|
7566
|
+
this.id = ArrayBuffer.isView(id) ? ClientIdentification.decode(id) : id;
|
|
7536
7567
|
this.signedDrmCertificate = SignedDrmCertificate.decode(this.id.token);
|
|
7537
7568
|
this.drmCertificate = DrmCertificate.decode(
|
|
7538
7569
|
this.signedDrmCertificate.drmCertificate
|
|
@@ -7549,23 +7580,39 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7549
7580
|
const key = await this.exportKey();
|
|
7550
7581
|
return [id, key];
|
|
7551
7582
|
}
|
|
7552
|
-
async pack(format = "
|
|
7553
|
-
if (format
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7583
|
+
async pack(format = "azot") {
|
|
7584
|
+
if (format === "wvd") {
|
|
7585
|
+
const id = ClientIdentification.encode(this.id).finish();
|
|
7586
|
+
const key = await this.exportKey();
|
|
7587
|
+
const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
|
|
7588
|
+
const keyDerBinary = fromBase64(keyDer).toBuffer();
|
|
7589
|
+
const [type] = types.entries().find(([, type2]) => type2 === this.type);
|
|
7590
|
+
const wvd = buildWvd({
|
|
7591
|
+
clientId: id,
|
|
7592
|
+
deviceType: type,
|
|
7593
|
+
securityLevel: this.securityLevel,
|
|
7594
|
+
privateKey: keyDerBinary
|
|
7595
|
+
});
|
|
7596
|
+
return wvd;
|
|
7597
|
+
} else if (format === "azot") {
|
|
7598
|
+
const clientIdentification = this.id.toJSON();
|
|
7599
|
+
const privateKey = await this.exportKey();
|
|
7600
|
+
const payload = {
|
|
7601
|
+
version: 1,
|
|
7602
|
+
type: "widevine-client",
|
|
7603
|
+
data: {
|
|
7604
|
+
clientIdentification,
|
|
7605
|
+
privateKey: fromBuffer(privateKey).toText()
|
|
7606
|
+
}
|
|
7607
|
+
};
|
|
7608
|
+
const data = JSON.stringify(payload, null, 2);
|
|
7609
|
+
return fromText(data).toBuffer();
|
|
7610
|
+
} else {
|
|
7611
|
+
throw new Error("Unsupported format");
|
|
7612
|
+
}
|
|
7566
7613
|
}
|
|
7567
7614
|
async importKey(pkcs1) {
|
|
7568
|
-
const pkcs1pem = fromBuffer(pkcs1).toText();
|
|
7615
|
+
const pkcs1pem = typeof pkcs1 === "string" ? pkcs1 : fromBuffer(pkcs1).toText();
|
|
7569
7616
|
const pkcs8pem = toPKCS8(pkcs1pem);
|
|
7570
7617
|
const pemContents = pkcs8pem.split("\n").slice(1, -2).join("\n");
|
|
7571
7618
|
const data = fromBase64(pemContents).toBuffer();
|
package/package.json
CHANGED