Skip to content

Commit ca6c9fd

Browse files
committed
feat: add cli, library, upgrade extension to manifest v3 and new design
1 parent b46b674 commit ca6c9fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+31118
-340
lines changed

.gitignore

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
.output
12+
stats.html
13+
stats-*.json
14+
.wxt
15+
web-ext.config.ts
16+
dist
17+
18+
# Editor directories and files
19+
.vscode/*
20+
!.vscode/extensions.json
21+
.idea
122
.DS_Store
2-
streamyx-extension.crx
3-
streamyx-extension.pem
23+
*.suo
24+
*.ntvs*
25+
*.njsproj
26+
*.sln
27+
*.sw?
28+
29+
device_client_id_blob
30+
device_private_key
31+
*.wvd

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
1-
# Streamyx Chrome Extension
1+
# azot
22

3-
Intercept [DRM](https://www.widevine.com/) requests, view logs from [EME](https://w3c.github.io/encrypted-media/) session and copy ready commands for [Streamyx CLI](https://github.com/vitalygashkov/streamyx)
3+
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).
4+
5+
## Features
6+
7+
- **Network-independent interception**: everything works even with one-time tokens and custom request body/response format.
8+
- **Custom client support**: WVD v2, device_client_id_blob + device_private_key, client_id.bin + private_key.pem
9+
- **Logging** details from EME events in Developer Tools console of current page
10+
- **Manifest V3** compliant browser extension
11+
- **Converting clients** between formats via CLI
12+
- **Encrypted Media Extensions API** compatibility via `requestMediaKeySystemAccess()` method
13+
- **Runtime agnostic** core: works in Node.js, Bun, Deno, browsers and more
14+
- **Minimal** dependencies
415

516
## Installation
617

7-
1. Enable **Developer Mode** in [Chrome Extensions](chrome://extensions/)
18+
> JavaScript library and command-line tool installation requires pre-installed JavaScript runtime (e.g. Node.js).
19+
20+
### JavaScript library
21+
22+
```bash
23+
npm install azot
24+
```
25+
26+
### Command-line tool
27+
28+
```bash
29+
npm install -g azot
30+
```
31+
32+
### Browser extension
33+
34+
1. Enable **Developer Mode** in [Chrome Extensions](chrome://extensions/) page
835
2. Click on **Load Unpacked**
9-
3. Select folder where you extracted zip downloaded from [Releases](https://github.com/vitalygashkov/streamyx-extension/releases) page
36+
3. Select folder where you extracted zip downloaded from [Releases](https://github.com/vitalygashkov/azot/releases) page
1037
4. Done!
1138

12-
## Features
39+
## Usage
40+
41+
### Example with Encrypted Media Extensions API
42+
43+
Source code for example available [here](https://github.com/vitalygashkov/orlan/blob/main/examples/demo/eme.js). It's a minimal example of using `azot` to get a license for Bitmovin's Art of Motion Demo. This example similar to [example from EME](https://www.w3.org/TR/encrypted-media-2/#example-8).
44+
45+
There are some differences from the native EME implementation:
1346

14-
- **Notifications** about intercepted request with button to **copy Streamyx command** for your shell (run it using [Streamyx](https://github.com/vitalygashkov/streamyx) >= v4.0.0-beta.46 to extract content decryption keys)
15-
- [Encrypted Media Extensions (EME)](https://w3c.github.io/encrypted-media/) logging in Developer Tools console (**session ID, key ID, PSSH**, etc.)
16-
- Request **blocking** to catch license requests with disposable / one time tokens (you can add your own list of servers to block in `./src/worker.js`)
47+
1. You must import your Widevine client to obtain a license.
48+
2. In the `keyStatuses` field, Map's key is not just the key ID (like in EME), but pair with ID and value. For example, if key ID in HEX is `35e7eff366b24121a261832f3368146f` and content key itself is `f01471043a064e039a602346845b690f` then Map's key will be `35e7eff366b24121a261832f3368146f:f01471043a064e039a602346845b690f` (but as binary with `BufferSource` type) instead of just `35e7eff366b24121a261832f3368146f`.

cli/commands/client/help.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { col } from '../../utils';
2+
3+
export const help = () => {
4+
console.log(`azot client: Widevine client utilities\n`);
5+
console.log(`Usage: azot client <subcommand> [...flags]\n`);
6+
console.log(`Commands:`);
7+
console.log(
8+
col(`info <input>`) +
9+
'print info about client that is on <input> path (*.wvd filepath or directory with id and private key)',
10+
);
11+
console.log(
12+
col(`pack <input> <output>`) +
13+
'pack client id and private key from <input> directory into single *.wvd file with <output> path',
14+
);
15+
console.log(
16+
col(`unpack <input> <output>`) +
17+
'unpack *.wvd from <input> path to separate client id and private key placed in <output> directory',
18+
);
19+
console.log('');
20+
console.log(`Flags:`);
21+
console.log(col(`-h, --help`) + 'Display this menu and exit');
22+
};

cli/commands/client/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { help } from './help';
2+
import { info } from './info';
3+
import { pack } from './pack';
4+
import { unpack } from './unpack';
5+
6+
export const client = { info, pack, unpack, help };

cli/commands/client/info.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { importClient } from '../../utils';
2+
3+
export const info = async (input: string) => {
4+
const client = await importClient(input);
5+
for (const [key, value] of client.info.entries()) {
6+
console.log(`${key}: ${value}`);
7+
}
8+
};

cli/commands/client/pack.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { writeFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import { importClient } from '../../utils';
4+
5+
export const pack = async (input = process.cwd(), output?: string) => {
6+
const client = await importClient(input);
7+
const wvd = await client.toWvd();
8+
const wvdName = `${client.info.get('company_name')}_${client.info.get('model_name')}`;
9+
const wvdOutput = output || join(process.cwd(), `${wvdName}.wvd`);
10+
await writeFile(wvdOutput, wvd);
11+
console.log(`Client packed: ${wvdOutput}`);
12+
};

cli/commands/client/unpack.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { writeFile } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import { importClient } from '../../utils';
4+
5+
export const unpack = async (input = process.cwd(), output?: string) => {
6+
const client = await importClient(input);
7+
const [id, key] = await client.toPair();
8+
const idOutput = join(output || process.cwd(), `device_client_id_blob`);
9+
const keyOutput = join(output || process.cwd(), `device_private_key`);
10+
await writeFile(idOutput, id);
11+
await writeFile(keyOutput, key);
12+
console.log(`Client unpacked: ${idOutput}, ${keyOutput}`);
13+
};

cli/commands/license/help.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { col } from '../../utils';
2+
3+
export const help = () => {
4+
console.log(`azot license: Make license request\n`);
5+
console.log(`Usage: azot license <url> [...flags]\n`);
6+
console.log(`Commands:`);
7+
console.log(
8+
col(`<url>`) +
9+
'URL of license server (e.g. https://cwip-shaka-proxy.appspot.com/no_auth)',
10+
);
11+
console.log('');
12+
console.log(`Flags:`);
13+
console.log(col(`-H, --header`) + 'headers to send with license request');
14+
console.log(col(`-p, --pssh`) + 'widevine PSSH data in Base64');
15+
console.log(
16+
col(`-c, --client`) +
17+
'path to client (directory with id and private key or path to *.wvd file)',
18+
);
19+
console.log(
20+
col(`-e, --encrypt`) +
21+
'enable client encryption with service certificate, disabled by default',
22+
);
23+
console.log(col(`-h, --help`) + 'display this menu and exit');
24+
};

cli/commands/license/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './license';

0 commit comments

Comments
 (0)