azot 0.7.2 → 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 +116 -26
- package/dist/lib/main.d.cts +23 -7
- package/dist/lib/main.d.ts +23 -7
- package/dist/lib/main.js +115 -26
- 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.
|
|
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
|
@@ -34,6 +34,7 @@ __export(main_exports, {
|
|
|
34
34
|
Client: () => Client,
|
|
35
35
|
Key: () => Key,
|
|
36
36
|
Session: () => Session,
|
|
37
|
+
connect: () => connect,
|
|
37
38
|
fetchDecryptionKeys: () => fetchDecryptionKeys,
|
|
38
39
|
fromBase64: () => fromBase64,
|
|
39
40
|
fromBinary: () => fromBinary,
|
|
@@ -7235,6 +7236,7 @@ var Session = class extends EventTarget {
|
|
|
7235
7236
|
deriveContext(licenseRequest.bytes)
|
|
7236
7237
|
);
|
|
7237
7238
|
this.dispatchEvent(new MessageEvent("license-request", message.bytes));
|
|
7239
|
+
return message.bytes;
|
|
7238
7240
|
}
|
|
7239
7241
|
async waitForLicenseRequest() {
|
|
7240
7242
|
return new Promise((resolve) => {
|
|
@@ -7361,6 +7363,9 @@ var Session = class extends EventTarget {
|
|
|
7361
7363
|
"usable"
|
|
7362
7364
|
);
|
|
7363
7365
|
}
|
|
7366
|
+
async getKeys() {
|
|
7367
|
+
return Array.from(this.keys.values());
|
|
7368
|
+
}
|
|
7364
7369
|
close() {
|
|
7365
7370
|
this.dispatchEvent(new Event("closed"));
|
|
7366
7371
|
return Promise.resolve();
|
|
@@ -7400,6 +7405,58 @@ var defaultResponseFilter = (response, log = console) => {
|
|
|
7400
7405
|
return response;
|
|
7401
7406
|
};
|
|
7402
7407
|
|
|
7408
|
+
// src/lib/connect.ts
|
|
7409
|
+
var connect = ({ baseUrl, secret }) => {
|
|
7410
|
+
const headers = {
|
|
7411
|
+
"x-secret-key": secret,
|
|
7412
|
+
"content-type": "application/json"
|
|
7413
|
+
};
|
|
7414
|
+
const json = (data) => JSON.stringify(data);
|
|
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
|
+
}
|
|
7436
|
+
};
|
|
7437
|
+
const createSession = async (sessionType, client) => {
|
|
7438
|
+
const data = await http.post(`/session`, { client });
|
|
7439
|
+
const sessionId = data.id;
|
|
7440
|
+
const generateRequest = async (initDataType, initData) => {
|
|
7441
|
+
const data2 = await http.post(`/session/${sessionId}/generate-request`, {
|
|
7442
|
+
initDataType,
|
|
7443
|
+
initData: fromBuffer(initData).toBase64()
|
|
7444
|
+
});
|
|
7445
|
+
return fromBase64(data2.licenseRequest).toBuffer();
|
|
7446
|
+
};
|
|
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 };
|
|
7456
|
+
};
|
|
7457
|
+
return { createSession };
|
|
7458
|
+
};
|
|
7459
|
+
|
|
7403
7460
|
// src/lib/wvd.ts
|
|
7404
7461
|
var VERSION = 2;
|
|
7405
7462
|
var MAGIC = fromText("WVD").toBuffer();
|
|
@@ -7509,18 +7566,34 @@ var Client = class _Client {
|
|
|
7509
7566
|
vmp;
|
|
7510
7567
|
info;
|
|
7511
7568
|
#key;
|
|
7512
|
-
static async fromPacked(data, format = "
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
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-----
|
|
7516
7574
|
${fromBuffer(parsed.privateKey).toBase64()}
|
|
7517
7575
|
-----END RSA PRIVATE KEY-----`;
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
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
|
+
}
|
|
7524
7597
|
}
|
|
7525
7598
|
static async fromUnpacked(id, key, vmp) {
|
|
7526
7599
|
const client = new _Client(id);
|
|
@@ -7536,7 +7609,7 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7536
7609
|
return this.#key;
|
|
7537
7610
|
}
|
|
7538
7611
|
constructor(id, type = CLIENT_TYPE.android, securityLevel = 3) {
|
|
7539
|
-
this.id = ClientIdentification.decode(id);
|
|
7612
|
+
this.id = ArrayBuffer.isView(id) ? ClientIdentification.decode(id) : id;
|
|
7540
7613
|
this.signedDrmCertificate = SignedDrmCertificate.decode(this.id.token);
|
|
7541
7614
|
this.drmCertificate = DrmCertificate.decode(
|
|
7542
7615
|
this.signedDrmCertificate.drmCertificate
|
|
@@ -7553,23 +7626,39 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7553
7626
|
const key = await this.exportKey();
|
|
7554
7627
|
return [id, key];
|
|
7555
7628
|
}
|
|
7556
|
-
async pack(format = "
|
|
7557
|
-
if (format
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
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
|
+
}
|
|
7570
7659
|
}
|
|
7571
7660
|
async importKey(pkcs1) {
|
|
7572
|
-
const pkcs1pem = fromBuffer(pkcs1).toText();
|
|
7661
|
+
const pkcs1pem = typeof pkcs1 === "string" ? pkcs1 : fromBuffer(pkcs1).toText();
|
|
7573
7662
|
const pkcs8pem = toPKCS8(pkcs1pem);
|
|
7574
7663
|
const pemContents = pkcs8pem.split("\n").slice(1, -2).join("\n");
|
|
7575
7664
|
const data = fromBase64(pemContents).toBuffer();
|
|
@@ -7720,6 +7809,7 @@ var fetchDecryptionKeys = async (params) => {
|
|
|
7720
7809
|
Client,
|
|
7721
7810
|
Key,
|
|
7722
7811
|
Session,
|
|
7812
|
+
connect,
|
|
7723
7813
|
fetchDecryptionKeys,
|
|
7724
7814
|
fromBase64,
|
|
7725
7815
|
fromBinary,
|
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
|
}>;
|
|
@@ -2396,6 +2397,21 @@ declare class Client {
|
|
|
2396
2397
|
};
|
|
2397
2398
|
}
|
|
2398
2399
|
|
|
2400
|
+
type ConnectParams = {
|
|
2401
|
+
baseUrl: string;
|
|
2402
|
+
secret: string;
|
|
2403
|
+
};
|
|
2404
|
+
declare const connect: ({ baseUrl, secret }: ConnectParams) => {
|
|
2405
|
+
createSession: (sessionType: SessionType, client: string) => Promise<{
|
|
2406
|
+
sessionId: any;
|
|
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>;
|
|
2412
|
+
}>;
|
|
2413
|
+
};
|
|
2414
|
+
|
|
2399
2415
|
declare const fromText: (data: string) => {
|
|
2400
2416
|
toBase64: () => string;
|
|
2401
2417
|
toHex: () => string;
|
|
@@ -2435,4 +2451,4 @@ interface FetchDecryptionKeysParams {
|
|
|
2435
2451
|
}
|
|
2436
2452
|
declare const fetchDecryptionKeys: (params: FetchDecryptionKeysParams) => Promise<Key[]>;
|
|
2437
2453
|
|
|
2438
|
-
export { CLIENT_TYPE, Client, type ClientType, Key, SecurityLevel, Session, fetchDecryptionKeys, fromBase64, fromBinary, fromBuffer, fromHex, fromText };
|
|
2454
|
+
export { CLIENT_TYPE, Client, type ClientType, Key, SecurityLevel, Session, connect, fetchDecryptionKeys, fromBase64, fromBinary, fromBuffer, fromHex, fromText };
|
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
|
}>;
|
|
@@ -2396,6 +2397,21 @@ declare class Client {
|
|
|
2396
2397
|
};
|
|
2397
2398
|
}
|
|
2398
2399
|
|
|
2400
|
+
type ConnectParams = {
|
|
2401
|
+
baseUrl: string;
|
|
2402
|
+
secret: string;
|
|
2403
|
+
};
|
|
2404
|
+
declare const connect: ({ baseUrl, secret }: ConnectParams) => {
|
|
2405
|
+
createSession: (sessionType: SessionType, client: string) => Promise<{
|
|
2406
|
+
sessionId: any;
|
|
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>;
|
|
2412
|
+
}>;
|
|
2413
|
+
};
|
|
2414
|
+
|
|
2399
2415
|
declare const fromText: (data: string) => {
|
|
2400
2416
|
toBase64: () => string;
|
|
2401
2417
|
toHex: () => string;
|
|
@@ -2435,4 +2451,4 @@ interface FetchDecryptionKeysParams {
|
|
|
2435
2451
|
}
|
|
2436
2452
|
declare const fetchDecryptionKeys: (params: FetchDecryptionKeysParams) => Promise<Key[]>;
|
|
2437
2453
|
|
|
2438
|
-
export { CLIENT_TYPE, Client, type ClientType, Key, SecurityLevel, Session, fetchDecryptionKeys, fromBase64, fromBinary, fromBuffer, fromHex, fromText };
|
|
2454
|
+
export { CLIENT_TYPE, Client, type ClientType, Key, SecurityLevel, Session, connect, fetchDecryptionKeys, fromBase64, fromBinary, fromBuffer, fromHex, fromText };
|
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();
|
|
@@ -7355,6 +7359,58 @@ var defaultResponseFilter = (response, log = console) => {
|
|
|
7355
7359
|
return response;
|
|
7356
7360
|
};
|
|
7357
7361
|
|
|
7362
|
+
// src/lib/connect.ts
|
|
7363
|
+
var connect = ({ baseUrl, secret }) => {
|
|
7364
|
+
const headers = {
|
|
7365
|
+
"x-secret-key": secret,
|
|
7366
|
+
"content-type": "application/json"
|
|
7367
|
+
};
|
|
7368
|
+
const json = (data) => JSON.stringify(data);
|
|
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
|
+
}
|
|
7390
|
+
};
|
|
7391
|
+
const createSession = async (sessionType, client) => {
|
|
7392
|
+
const data = await http.post(`/session`, { client });
|
|
7393
|
+
const sessionId = data.id;
|
|
7394
|
+
const generateRequest = async (initDataType, initData) => {
|
|
7395
|
+
const data2 = await http.post(`/session/${sessionId}/generate-request`, {
|
|
7396
|
+
initDataType,
|
|
7397
|
+
initData: fromBuffer(initData).toBase64()
|
|
7398
|
+
});
|
|
7399
|
+
return fromBase64(data2.licenseRequest).toBuffer();
|
|
7400
|
+
};
|
|
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 };
|
|
7410
|
+
};
|
|
7411
|
+
return { createSession };
|
|
7412
|
+
};
|
|
7413
|
+
|
|
7358
7414
|
// src/lib/wvd.ts
|
|
7359
7415
|
var VERSION = 2;
|
|
7360
7416
|
var MAGIC = fromText("WVD").toBuffer();
|
|
@@ -7464,18 +7520,34 @@ var Client = class _Client {
|
|
|
7464
7520
|
vmp;
|
|
7465
7521
|
info;
|
|
7466
7522
|
#key;
|
|
7467
|
-
static async fromPacked(data, format = "
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
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-----
|
|
7471
7528
|
${fromBuffer(parsed.privateKey).toBase64()}
|
|
7472
7529
|
-----END RSA PRIVATE KEY-----`;
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
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
|
+
}
|
|
7479
7551
|
}
|
|
7480
7552
|
static async fromUnpacked(id, key, vmp) {
|
|
7481
7553
|
const client = new _Client(id);
|
|
@@ -7491,7 +7563,7 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7491
7563
|
return this.#key;
|
|
7492
7564
|
}
|
|
7493
7565
|
constructor(id, type = CLIENT_TYPE.android, securityLevel = 3) {
|
|
7494
|
-
this.id = ClientIdentification.decode(id);
|
|
7566
|
+
this.id = ArrayBuffer.isView(id) ? ClientIdentification.decode(id) : id;
|
|
7495
7567
|
this.signedDrmCertificate = SignedDrmCertificate.decode(this.id.token);
|
|
7496
7568
|
this.drmCertificate = DrmCertificate.decode(
|
|
7497
7569
|
this.signedDrmCertificate.drmCertificate
|
|
@@ -7508,23 +7580,39 @@ ${fromBuffer(parsed.privateKey).toBase64()}
|
|
|
7508
7580
|
const key = await this.exportKey();
|
|
7509
7581
|
return [id, key];
|
|
7510
7582
|
}
|
|
7511
|
-
async pack(format = "
|
|
7512
|
-
if (format
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
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
|
+
}
|
|
7525
7613
|
}
|
|
7526
7614
|
async importKey(pkcs1) {
|
|
7527
|
-
const pkcs1pem = fromBuffer(pkcs1).toText();
|
|
7615
|
+
const pkcs1pem = typeof pkcs1 === "string" ? pkcs1 : fromBuffer(pkcs1).toText();
|
|
7528
7616
|
const pkcs8pem = toPKCS8(pkcs1pem);
|
|
7529
7617
|
const pemContents = pkcs8pem.split("\n").slice(1, -2).join("\n");
|
|
7530
7618
|
const data = fromBase64(pemContents).toBuffer();
|
|
@@ -7674,6 +7762,7 @@ export {
|
|
|
7674
7762
|
Client,
|
|
7675
7763
|
Key,
|
|
7676
7764
|
Session,
|
|
7765
|
+
connect,
|
|
7677
7766
|
fetchDecryptionKeys,
|
|
7678
7767
|
fromBase64,
|
|
7679
7768
|
fromBinary,
|