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 CHANGED
@@ -4,7 +4,7 @@
4
4
  ![GitHub Downloads (all assets, latest release)](https://img.shields.io/github/downloads/vitalygashkov/azot/latest/total?style=flat&color=black)
5
5
  [![npm downloads](https://img.shields.io/npm/dt/azot?style=flat&color=black)](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 [Google's](https://about.google/) [Widevine](https://www.widevine.com/about) [DRM](https://www.urbandictionary.com/define.php?term=DRM).
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 = "WVDv2") {
7480
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7481
- const parsed = parseWvd(data);
7482
- const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
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
- const key = fromText(pcks1).toBuffer();
7486
- const type = types.get(parsed.deviceType);
7487
- const securityLevel = parsed.securityLevel;
7488
- const client2 = new _Client(parsed.clientId, type, securityLevel);
7489
- await client2.importKey(key);
7490
- return client2;
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 = "WVDv2") {
7524
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7525
- const id = ClientIdentification.encode(this.id).finish();
7526
- const key = await this.exportKey();
7527
- const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
7528
- const keyDerBinary = fromBase64(keyDer).toBuffer();
7529
- const [type] = types.entries().find(([, type2]) => type2 === this.type);
7530
- const wvd = buildWvd({
7531
- clientId: id,
7532
- deviceType: type,
7533
- securityLevel: this.securityLevel,
7534
- privateKey: keyDerBinary
7535
- });
7536
- return wvd;
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 wvdFilename = entries.find((entry) => entry.endsWith("wvd"));
7729
+ const packedFilename = entries.find(
7730
+ (entry) => entry.endsWith("wvd") || entry.endsWith("azot")
7731
+ );
7694
7732
  const isUnpacked = !!(idFilename && keyFilename);
7695
- const isPacked = !!wvdFilename;
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 wvdPath = (0, import_node_path.join)(input, wvdFilename);
7704
- const wvd = await (0, import_promises.readFile)(wvdPath);
7705
- return await Client.fromPacked(wvd);
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 *.wvd file with <output> path"
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 *.wvd from <input> path to separate client id and private key placed in <output> directory"
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 wvd = await client2.pack();
7755
- const wvdName = `${client2.info.get("company_name")}_${client2.info.get("model_name")}`;
7756
- const wvdOutput = output || (0, import_node_path2.join)(process.cwd(), `${wvdName}.wvd`);
7757
- await (0, import_promises2.writeFile)(wvdOutput, wvd);
7758
- console.log(`Client packed: ${wvdOutput}`);
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.7.2",
7824
- description: "Swiss Army knife for pentesting DRMs",
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 = "WVDv2") {
7513
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7514
- const parsed = parseWvd(data);
7515
- const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
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
- const key = fromText(pcks1).toBuffer();
7519
- const type = types.get(parsed.deviceType);
7520
- const securityLevel = parsed.securityLevel;
7521
- const client = new _Client(parsed.clientId, type, securityLevel);
7522
- await client.importKey(key);
7523
- return client;
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 = "WVDv2") {
7557
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7558
- const id = ClientIdentification.encode(this.id).finish();
7559
- const key = await this.exportKey();
7560
- const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
7561
- const keyDerBinary = fromBase64(keyDer).toBuffer();
7562
- const [type] = types.entries().find(([, type2]) => type2 === this.type);
7563
- const wvd = buildWvd({
7564
- clientId: id,
7565
- deviceType: type,
7566
- securityLevel: this.securityLevel,
7567
- privateKey: keyDerBinary
7568
- });
7569
- return wvd;
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,
@@ -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 implements MediaKeySession {
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<void>;
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?: 'WVDv2'): Promise<Client>;
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?: 'WVDv2'): Promise<Uint8Array>;
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 };
@@ -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 implements MediaKeySession {
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<void>;
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?: 'WVDv2'): Promise<Client>;
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?: 'WVDv2'): Promise<Uint8Array>;
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 = "WVDv2") {
7468
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7469
- const parsed = parseWvd(data);
7470
- const pcks1 = `-----BEGIN RSA PRIVATE KEY-----
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
- const key = fromText(pcks1).toBuffer();
7474
- const type = types.get(parsed.deviceType);
7475
- const securityLevel = parsed.securityLevel;
7476
- const client = new _Client(parsed.clientId, type, securityLevel);
7477
- await client.importKey(key);
7478
- return client;
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 = "WVDv2") {
7512
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7513
- const id = ClientIdentification.encode(this.id).finish();
7514
- const key = await this.exportKey();
7515
- const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
7516
- const keyDerBinary = fromBase64(keyDer).toBuffer();
7517
- const [type] = types.entries().find(([, type2]) => type2 === this.type);
7518
- const wvd = buildWvd({
7519
- clientId: id,
7520
- deviceType: type,
7521
- securityLevel: this.securityLevel,
7522
- privateKey: keyDerBinary
7523
- });
7524
- return wvd;
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,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "azot",
3
- "version": "0.7.2",
4
- "description": "Swiss Army knife for pentesting DRMs",
3
+ "version": "0.8.3",
4
+ "description": "The ultimate toolkit for pentesting DRM systems",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",