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 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.8.0",
7824
- description: "Swiss Army knife for pentesting DRM systems",
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 post = async (route, body) => {
7412
- const response = await fetch(`${baseUrl}${route}`, {
7413
- method: "POST",
7414
- headers,
7415
- body: json(body)
7416
- });
7417
- const contentLength = response.headers.get("content-length");
7418
- if (contentLength === "0") return;
7419
- return response.json();
7420
- };
7421
- const get = async (route) => {
7422
- const response = await fetch(`${baseUrl}${route}`, {
7423
- method: "GET",
7424
- headers
7425
- });
7426
- return response.json();
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 (initData, initDataType) => {
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) => post(`/session/${sessionId}/update`, { response });
7439
- const keys = async () => get(`/session/${sessionId}/keys`);
7440
- return { sessionId, generateRequest, update, keys };
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 = "WVDv2") {
7555
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7556
- const parsed = parseWvd(data);
7557
- 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-----
7558
7574
  ${fromBuffer(parsed.privateKey).toBase64()}
7559
7575
  -----END RSA PRIVATE KEY-----`;
7560
- const key = fromText(pcks1).toBuffer();
7561
- const type = types.get(parsed.deviceType);
7562
- const securityLevel = parsed.securityLevel;
7563
- const client = new _Client(parsed.clientId, type, securityLevel);
7564
- await client.importKey(key);
7565
- 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
+ }
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 = "WVDv2") {
7599
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7600
- const id = ClientIdentification.encode(this.id).finish();
7601
- const key = await this.exportKey();
7602
- const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
7603
- const keyDerBinary = fromBase64(keyDer).toBuffer();
7604
- const [type] = types.entries().find(([, type2]) => type2 === this.type);
7605
- const wvd = buildWvd({
7606
- clientId: id,
7607
- deviceType: type,
7608
- securityLevel: this.securityLevel,
7609
- privateKey: keyDerBinary
7610
- });
7611
- 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
+ }
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();
@@ -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
  }>;
@@ -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: (initData: string, initDataType: string) => Promise<any>;
2407
- update: (response: string) => Promise<any>;
2408
- keys: () => Promise<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>;
2409
2412
  }>;
2410
2413
  };
2411
2414
 
@@ -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
  }>;
@@ -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: (initData: string, initDataType: string) => Promise<any>;
2407
- update: (response: string) => Promise<any>;
2408
- keys: () => Promise<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>;
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 post = async (route, body) => {
7366
- const response = await fetch(`${baseUrl}${route}`, {
7367
- method: "POST",
7368
- headers,
7369
- body: json(body)
7370
- });
7371
- const contentLength = response.headers.get("content-length");
7372
- if (contentLength === "0") return;
7373
- return response.json();
7374
- };
7375
- const get = async (route) => {
7376
- const response = await fetch(`${baseUrl}${route}`, {
7377
- method: "GET",
7378
- headers
7379
- });
7380
- return response.json();
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 (initData, initDataType) => {
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) => post(`/session/${sessionId}/update`, { response });
7393
- const keys = async () => get(`/session/${sessionId}/keys`);
7394
- return { sessionId, generateRequest, update, keys };
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 = "WVDv2") {
7509
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7510
- const parsed = parseWvd(data);
7511
- 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-----
7512
7528
  ${fromBuffer(parsed.privateKey).toBase64()}
7513
7529
  -----END RSA PRIVATE KEY-----`;
7514
- const key = fromText(pcks1).toBuffer();
7515
- const type = types.get(parsed.deviceType);
7516
- const securityLevel = parsed.securityLevel;
7517
- const client = new _Client(parsed.clientId, type, securityLevel);
7518
- await client.importKey(key);
7519
- 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
+ }
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 = "WVDv2") {
7553
- if (format !== "WVDv2") throw new Error("Only WVDv2 is supported");
7554
- const id = ClientIdentification.encode(this.id).finish();
7555
- const key = await this.exportKey();
7556
- const keyDer = fromBuffer(key).toText().split("\n").map((s) => s.trim()).slice(1, -1).join("\n");
7557
- const keyDerBinary = fromBase64(keyDer).toBuffer();
7558
- const [type] = types.entries().find(([, type2]) => type2 === this.type);
7559
- const wvd = buildWvd({
7560
- clientId: id,
7561
- deviceType: type,
7562
- securityLevel: this.securityLevel,
7563
- privateKey: keyDerBinary
7564
- });
7565
- 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
+ }
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "azot",
3
- "version": "0.8.0",
4
- "description": "Swiss Army knife for pentesting DRM systems",
3
+ "version": "0.8.3",
4
+ "description": "The ultimate toolkit for pentesting DRM systems",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",