mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 12:05:07 +00:00
The first of the widevine / playready config keys in a cdm entry sets which device loads first and which DRM system licenses a track that offers the two. An entry that lists playready first changes from Widevine to PlayReady.
578 lines
23 KiB
Markdown
578 lines
23 KiB
Markdown
# DRM & CDM Setup
|
|
|
|
A Digital Rights Management (DRM) system protects most commercial streaming titles.
|
|
To download a title and play it back, unshackle needs to get the **content keys**
|
|
for the encrypted tracks and then decrypt them. This page explains the DRM systems
|
|
unshackle supports, how to install a device so it can talk to a license server, the
|
|
difference between local and remote CDMs, and how to choose a decryption backend.
|
|
|
|
!!! note "Who this page is for"
|
|
This is a practical configuration guide for end users of the unshackle CLI.
|
|
A few sections have a **Developer** mark. They give internal behaviour that is
|
|
useful when you write a service, but you do not need them to download a protected
|
|
title.
|
|
|
|
## Key concepts
|
|
|
|
Before configuring anything, it helps to know the vocabulary. unshackle draws a firm
|
|
line between two things:
|
|
|
|
- A **CDM** (Content Decryption Module) is the trusted "black box" that turns a
|
|
*license challenge* into *content keys*. A **device** backs it: a file that carries
|
|
the cryptographic identity of a real client, such as a phone or a browser.
|
|
- A **DRM system** is the wrapper around a CDM that parses the protection metadata,
|
|
drives the challenge → license → keys handshake, and finally invokes an external
|
|
tool to physically decrypt the downloaded file.
|
|
|
|
A handful of other terms show up throughout:
|
|
|
|
| Term | Meaning |
|
|
|------|---------|
|
|
| **PSSH** | Protection System Specific Header. The per-system init data extracted from the file's init segment or the manifest. |
|
|
| **KID** | Key ID: a 16-byte UUID identifying *which* key a track needs. |
|
|
| **CEK** | Content Encryption Key: the AES content key, mapped from its KID. |
|
|
| **Challenge** | The license request the CDM produces, sent to the service's license server. |
|
|
| **License** | The server's response, which the CDM parses to recover the content keys. |
|
|
|
|
For a Widevine or PlayReady title, the flow is: unshackle reads the PSSH and KIDs
|
|
from the track, asks the CDM for a license challenge, sends that challenge to the
|
|
service's license server, hands the response back to the CDM to extract the keys, and
|
|
then decrypts the file with those keys.
|
|
|
|
## Supported DRM systems
|
|
|
|
unshackle implements five DRM systems. Only the first two need a device file and a
|
|
license-server handshake. The rest are special cases.
|
|
|
|
| System | Device file | License flow | Decryption |
|
|
|--------|-------------|--------------|------------|
|
|
| **Widevine** (Google) | `.wvd` | CDM challenge → license server → CDM parses → keys | shaka-packager / mp4decrypt |
|
|
| **PlayReady** (Microsoft) | `.prd` | CDM challenge (WRM header) → license server → CDM parses → keys | shaka-packager / mp4decrypt |
|
|
| **ClearKey (CENC)**, W3C EME `org.w3.clearkey` | none | JSON `{"kids":[...],"type":"temporary"}` → license server returns a JWK Set. No CDM. | shaka-packager / mp4decrypt |
|
|
| **ClearKey (HLS)**, AES-128 `EXT-X-KEY` | none | Key fetched directly from a URI or `data:` URL. No CDM, no challenge. | in-process AES-CBC |
|
|
| **MonaLisa** (proprietary, WASM) | `.mld` | Ticket from the service API; keys extracted locally via WASM. No license server. | ML-Worker binary + AES-ECB, per segment |
|
|
|
|
!!! warning "Two different ClearKeys"
|
|
"ClearKey (HLS)" and "ClearKey (CENC)" are entirely different systems that happen
|
|
to share a name. The HLS variant is the AES-128 content key that an `EXT-X-KEY`
|
|
tag references, and unshackle decrypts it in-process. The CENC variant is the W3C
|
|
EME `org.w3.clearkey` scheme. It delivers keys as a JWK Set, and shaka-packager or
|
|
mp4decrypt decrypts it like Widevine. Neither involves a device file.
|
|
|
|
### Widevine vs PlayReady
|
|
|
|
These are the two "real" DRMs, and in unshackle they behave almost identically: the
|
|
same PSSH/KID handling, the same decryption backends, and the same key-caching flow.
|
|
The differences that matter to you are:
|
|
|
|
- **Device file**: Widevine uses a `.wvd`. PlayReady uses a `.prd`.
|
|
- **Availability**: some services offer only one system, some offer both. When a
|
|
manifest carries both, unshackle picks the appropriate CDM for each track (see
|
|
[Widevine and PlayReady on the same title](#widevine-and-playready-on-the-same-title)).
|
|
|
|
To download a Widevine-protected title you need a `.wvd` device. For PlayReady you
|
|
need a `.prd` device. You can hold both, and unshackle automatically selects the
|
|
appropriate one for each track.
|
|
|
|
!!! info "Obtaining device files"
|
|
A `.wvd` or `.prd` encodes the private keys of a real client device. unshackle
|
|
does **not** ship with one and cannot make the underlying secret material for
|
|
you. You must supply your own. The `wvd` and `prd` commands below manage and
|
|
provision device files once you have the raw cryptographic material.
|
|
|
|
## Providing a device with the `wvd` and `prd` commands
|
|
|
|
Device files live in dedicated directories, by default:
|
|
|
|
| Kind | Default directory |
|
|
|------|-------------------|
|
|
| Widevine `.wvd` | `data/WVDs` |
|
|
| PlayReady `.prd` | `data/PRDs` |
|
|
|
|
You can move these with the `directories` section of your `unshackle.yaml`:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
directories:
|
|
wvds: /home/george/unshackle-data/WVDs
|
|
prds: /home/george/unshackle-data/PRDs
|
|
```
|
|
|
|
### Widevine devices: the `wvd` command
|
|
|
|
The `wvd` command group manages `.wvd` files.
|
|
|
|
=== "Add an existing device"
|
|
|
|
```console
|
|
$ unshackle wvd add ./nexus_6p.wvd
|
|
```
|
|
|
|
unshackle validates the file and moves it into the WVDs directory. You can add
|
|
several at once by passing multiple paths.
|
|
|
|
=== "Inspect a device"
|
|
|
|
```console
|
|
$ unshackle wvd parse nexus_6p
|
|
```
|
|
|
|
Prints the System ID, Security Level, Type, Flags, and whether a Private Key,
|
|
Client ID, and VMP are present. unshackle finds a bare name (no extension) inside
|
|
the WVDs directory. It uses a path with an extension as-is.
|
|
|
|
=== "Create from raw material"
|
|
|
|
```console
|
|
$ unshackle wvd new "Nexus 6P" ./private_key.pem ./client_id.bin ./vmp.bin -t ANDROID -l 3
|
|
```
|
|
|
|
Builds a `.wvd` from a PEM private key, a Client ID blob, and an optional
|
|
FileHashes (VMP) blob. `-t/--type` is the device type (default `Android`) and
|
|
`-l/--level` is the security level 1 to 3 (default `1`). The VMP argument is
|
|
optional.
|
|
|
|
=== "Delete / dump"
|
|
|
|
```console
|
|
$ unshackle wvd delete nexus_6p
|
|
$ unshackle wvd dump nexus_6p ./out
|
|
```
|
|
|
|
`delete` removes a device (with a confirmation prompt). `dump` extracts a
|
|
device's contents (metadata, private key, Client ID, VMP) into a folder.
|
|
|
|
!!! warning "v1 WVD files must be migrated"
|
|
If loading a device fails with a message about a "v1 WVD file", it was made with
|
|
an older format. Migrate it with pywidevine before use:
|
|
|
|
```console
|
|
$ pywidevine migrate --help
|
|
```
|
|
|
|
### PlayReady devices: the `prd` command
|
|
|
|
The `prd` command group creates and maintains `.prd` files.
|
|
|
|
=== "Create a new device"
|
|
|
|
```console
|
|
$ unshackle prd new ./zgpriv.dat ./bgroupcert.dat
|
|
```
|
|
|
|
Provision a new device from a group key and group certificate. You can instead
|
|
point at a single folder that contains `zgpriv.dat` and `bgroupcert.dat`:
|
|
|
|
```console
|
|
$ unshackle prd new ./device_folder/
|
|
```
|
|
|
|
unshackle makes a fresh leaf certificate and writes the device to the PRDs
|
|
directory (or to `-o/--output`). Use `-e/--encryption_key` and `-s/--signing_key`
|
|
to supply your own ECC keys instead of generating them.
|
|
|
|
=== "Reprovision"
|
|
|
|
```console
|
|
$ unshackle prd reprovision ./data/PRDs/mydevice.prd
|
|
```
|
|
|
|
Regenerates the leaf certificate and encryption/signing keys of an existing
|
|
device in place. The device must be able to reprovision (a group key must be
|
|
present, that is, a version 3 or higher device).
|
|
|
|
=== "Test against Microsoft's demo server"
|
|
|
|
```console
|
|
$ unshackle prd test ./data/PRDs/mydevice.prd -sl 2000 -c aesctr
|
|
```
|
|
|
|
Runs a full challenge/license/keys cycle against Microsoft's public PlayReady
|
|
test server and prints the returned keys, confirming the device works.
|
|
`-sl/--security-level` accepts `150`, `2000`, or `3000` (default `2000`).
|
|
`-c/--ckt` accepts `aesctr` or `aescbc`.
|
|
|
|
## Selecting which device to use: the `cdm` config
|
|
|
|
A device on disk is not enough. You must tell unshackle which device to use
|
|
for which service. That is the job of the `cdm` section, which maps a service tag to
|
|
a device name (the file's name without extension). A `default` entry acts as the
|
|
fallback:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
cdm:
|
|
default: nexus_6p_26830_l3
|
|
EXAMPLE1: chromecdm_l3
|
|
EXAMPLE2: sm_g935f_l1
|
|
```
|
|
|
|
Lookups are case-insensitive.
|
|
|
|
!!! tip "The device name is the file name"
|
|
A device named `chromecdm_l3` in the config resolves to `chromecdm_l3.wvd` in the
|
|
WVDs directory (or `chromecdm_l3.prd` in the PRDs directory). It may also be the
|
|
`name` of a remote CDM defined in `remote_cdm`. See
|
|
[Remote CDMs](#remote-cdms) below.
|
|
|
|
### Advanced selection: by quality, DRM, or profile
|
|
|
|
Instead of a plain name, a `cdm` entry can be a nested mapping, letting unshackle pick
|
|
a different device based on the track quality, the DRM system, or the active service
|
|
profile.
|
|
|
|
=== "By quality"
|
|
|
|
unshackle compares each config key against the video height. You can use exact
|
|
heights or comparisons (`>=1080`, `>720`, `<=576`, `<480`). The most specific one
|
|
wins.
|
|
|
|
```yaml
|
|
cdm:
|
|
SOMESERVICE:
|
|
">=1080": sm_g935f_l1
|
|
default: chromecdm_l3
|
|
```
|
|
|
|
=== "By DRM system"
|
|
|
|
Keys `widevine` and `playready` select a device per system. unshackle applies
|
|
these automatically, using the `widevine` device for Widevine tracks and the
|
|
`playready` device for PlayReady tracks.
|
|
|
|
```yaml
|
|
cdm:
|
|
SOMESERVICE:
|
|
widevine: sm_g935f_l1
|
|
playready: mydevice_sl3
|
|
```
|
|
|
|
=== "By profile"
|
|
|
|
unshackle compares each config key against the active profile, with a `default`
|
|
fallback.
|
|
|
|
```yaml
|
|
cdm:
|
|
SOMESERVICE:
|
|
premium: sm_g935f_l1
|
|
default: chromecdm_l3
|
|
```
|
|
|
|
## Local vs remote CDMs
|
|
|
|
A CDM can operate in two places:
|
|
|
|
- A **local CDM** loads a `.wvd` or `.prd` device from your machine and performs the
|
|
challenge/license/keys handshake locally. This is what the `cdm` mapping above
|
|
resolves to by default.
|
|
- A **remote CDM** delegates the CDM operations to an HTTP API. Your device (and its
|
|
secrets) live on the remote CDM host. unshackle sends it PSSH/challenge data and
|
|
receives keys back. This is useful for sharing a high-security-level device, using
|
|
a hosted content key service, or keeping device secrets off your download machine.
|
|
|
|
The device name in your `cdm` mapping resolves to a remote CDM whenever it matches the
|
|
`name` of an entry in the `remote_cdm` list. Otherwise it falls back to a local
|
|
`.prd`/`.wvd` file of that name.
|
|
|
|
## Remote CDMs
|
|
|
|
You declare remote CDMs as a list under `remote_cdm`. Each entry has a `name` (which
|
|
you then reference from the `cdm` mapping) and a shape that depends on its `type`.
|
|
There are four kinds.
|
|
|
|
### Widevine remote CDM (pywidevine serve)
|
|
|
|
With no `type` field and a non-PlayReady device type, unshackle treats the entry as a
|
|
standard pywidevine "serve" endpoint:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
remote_cdm:
|
|
- name: my_wv_remote
|
|
device_type: ANDROID
|
|
system_id: 26830
|
|
security_level: 3
|
|
host: https://cdm.example.com
|
|
secret: your-api-secret
|
|
device_name: sm_g935f_l1
|
|
|
|
cdm:
|
|
default: my_wv_remote
|
|
```
|
|
|
|
`security_level` defaults to `3000` if omitted. unshackle reads field names
|
|
case-insensitively, so `Device Type`/`device_type`, `System ID`/`system_id`, and the
|
|
other field names are all accepted.
|
|
|
|
### PlayReady remote CDM
|
|
|
|
If the device type is `PLAYREADY`, unshackle routes the entry to the pyplayready
|
|
remote CDM:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
remote_cdm:
|
|
- name: my_pr_remote
|
|
device_type: PLAYREADY
|
|
security_level: 3000
|
|
host: https://cdm.example.com
|
|
secret: your-api-secret
|
|
device_name: mydevice_sl3
|
|
```
|
|
|
|
!!! warning "The `host` must end in `/playready`"
|
|
For a PlayReady remote CDM the `host` URL has to include the trailing
|
|
`/playready` path segment (e.g. `https://cdm.example.com/playready`). pyplayready's
|
|
`RemoteCdm` treats `host` as a base and appends its own endpoint paths on top of it,
|
|
so a host without the suffix points the requests at the wrong location. The symptom
|
|
is `404` responses from the server rather than an obvious configuration error.
|
|
|
|
A pywidevine or pyplayready serve API can operate behind a reverse proxy or a CDN. Refer to
|
|
[Serve APIs behind a proxy](../reference/configuration/drm.md#remote_cdm).
|
|
|
|
### Decrypt Labs (hosted KeyXtractor API)
|
|
|
|
Set `type: decrypt_labs` to use Decrypt Labs' hosted KeyXtractor service, which
|
|
includes an intelligent key-caching layer:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
remote_cdm:
|
|
- name: decryptlabs
|
|
type: decrypt_labs
|
|
device_name: L1 # ChromeCDM, L1, L2, L3 (Widevine) or SL2, SL3 (PlayReady)
|
|
# host: https://keyxtractor.decryptlabs.com # optional, this is the default
|
|
# secret: ... # optional if decrypt_labs_api_key is set globally
|
|
|
|
decrypt_labs_api_key: your-decrypt-labs-key
|
|
```
|
|
|
|
unshackle sends the API key as the `decrypt-labs-api-key` header. If you do not write
|
|
`secret` on the entry, unshackle falls back to the global `decrypt_labs_api_key` value.
|
|
If neither is present it raises an error. The `host` defaults to
|
|
`https://keyxtractor.decryptlabs.com`. PlayReady devices (`SL2`/`SL3`) default to
|
|
security level `2000`/`3000` respectively. Widevine devices default to
|
|
`system_id: 26830`, `security_level: 3`.
|
|
|
|
### Custom API (`custom_api`)
|
|
|
|
For an arbitrary CDM HTTP API, `type: custom_api` provides a fully configurable client
|
|
that adapts to almost any request/response shape through YAML alone. A minimal example:
|
|
|
|
```yaml title="unshackle.yaml"
|
|
remote_cdm:
|
|
- name: my_custom
|
|
type: custom_api
|
|
device:
|
|
name: ChromeCDM
|
|
type: CHROME # CHROME, ANDROID, or PLAYREADY
|
|
system_id: 26830
|
|
security_level: 3
|
|
auth:
|
|
type: bearer # header, bearer, or basic
|
|
key: your-token
|
|
endpoints:
|
|
get_request:
|
|
path: /get-challenge
|
|
method: POST
|
|
decrypt_response:
|
|
path: /get-keys
|
|
method: POST
|
|
timeout: 30
|
|
```
|
|
|
|
The `custom_api` type supports much more: request/response field remapping,
|
|
transforms (base64/hex/JSON encoding, `kid:key` parsing), conditional parameters,
|
|
success/error detection, and vault-backed caching. Its full schema is for developers
|
|
who integrate a bespoke CDM API. See the developer note below.
|
|
|
|
!!! note "Developer: full `custom_api` schema"
|
|
A `custom_api` entry accepts these sub-objects:
|
|
|
|
- `device` (`name`, `type`, `system_id`, `security_level`)
|
|
- `auth` (`type` of `header`/`bearer`/`basic` with the matching credential fields,
|
|
plus `custom_headers`)
|
|
- `endpoints` (`get_request` and `decrypt_response`, each `{path, method, timeout}`)
|
|
- per-endpoint `request_mapping` (`param_names`, `static_params`,
|
|
`conditional_params`, `transforms`, `nested_params`, `exclude_params`) and
|
|
`response_mapping` (`fields`, `transforms`, `response_types`,
|
|
`success_conditions`, `error_fields`, `key_fields`)
|
|
- `caching` (`enabled`, `use_vaults`, `check_cached_first`)
|
|
- `legacy` and `timeout`
|
|
|
|
unshackle infers PlayReady mode when `device.type == PLAYREADY` or the device name
|
|
is `SL2`/`SL3`.
|
|
|
|
## Decryption backends
|
|
|
|
Once unshackle knows the content keys, one of two external tools decrypts the
|
|
encrypted file (for Widevine, PlayReady, and ClearKey-CENC):
|
|
|
|
- **shaka-packager** is the default.
|
|
- **mp4decrypt** is part of the Bento4 tools.
|
|
|
|
Both must be discoverable in unshackle's bundled `binaries/` directory, the
|
|
`binaries` folder of your user data directory, or on your `PATH`. If the selected tool's binary is missing at decrypt time, unshackle raises an
|
|
"executable not found but is required" error. See [dependencies](../getting-started/installation.md) for
|
|
installation.
|
|
|
|
You choose the backend with the `decryption` config key. It can be a single value applied
|
|
globally, or a per-service mapping:
|
|
|
|
=== "Global default"
|
|
|
|
```yaml title="unshackle.yaml"
|
|
decryption: shaka
|
|
```
|
|
|
|
=== "Per service"
|
|
|
|
```yaml title="unshackle.yaml"
|
|
decryption:
|
|
default: shaka
|
|
SOMESERVICE: mp4decrypt
|
|
```
|
|
|
|
!!! info "How the value is interpreted"
|
|
The default is `shaka`. Only the exact value `mp4decrypt` selects mp4decrypt.
|
|
**Anything else** (including `shaka`) selects shaka-packager. Per-service lookups
|
|
are case-insensitive, with the `default`/`DEFAULT` entry used as the fallback.
|
|
|
|
The two backends give equivalent output for a standard CENC title. In most
|
|
conditions shaka-packager is the better backend. Switch a specific service to
|
|
`mp4decrypt` if you find a track that shaka-packager cannot decrypt.
|
|
|
|
!!! note "ClearKey (HLS) does not use these tools"
|
|
unshackle decrypts an HLS AES-128 ClearKey title in-process with AES-CBC, and
|
|
ignores the `decryption` setting entirely. You do not need to configure anything.
|
|
|
|
## ClearKey
|
|
|
|
### ClearKey over HLS (AES-128)
|
|
|
|
When an HLS playlist references a content key with an `EXT-X-KEY` tag, unshackle
|
|
fetches the content key directly (from the given URI or an inline `data:` URL) and
|
|
decrypts each segment in-process with AES-CBC. There is no device, no CDM, and no
|
|
license challenge, so you do not set anything up. unshackle decrypts such tracks
|
|
automatically during download.
|
|
|
|
### ClearKey over CENC (W3C EME `org.w3.clearkey`)
|
|
|
|
The CENC ClearKey scheme is a proper license flow, but the "license server"
|
|
returns the keys as a JWK Set and not a DRM-wrapped blob. unshackle builds a W3C
|
|
EME request of the form:
|
|
|
|
```json
|
|
{"kids": ["<base64url-kid>", ...], "type": "temporary"}
|
|
```
|
|
|
|
It sends this to the manifest's license URL (or to whatever a service's ClearKey
|
|
license hook returns) and reads the JSON Web Key set back. You do not need a device
|
|
file. The decrypted file still goes through shaka-packager or mp4decrypt like any
|
|
other CENC track, so the `decryption` setting applies.
|
|
|
|
## Widevine and PlayReady on the same title
|
|
|
|
A single manifest can advertise both Widevine and PlayReady. unshackle picks the CDM
|
|
per track: if a track needs a system that your loaded CDM cannot supply, it
|
|
transparently switches to the appropriate device for that track using your `cdm`
|
|
mapping. If it cannot find a matching device, it fails with a clear message such as:
|
|
|
|
```
|
|
Title needs a Widevine CDM but SOMESERVICE is configured with PlayReady.
|
|
```
|
|
|
|
Use the `cdm` mapping's [DRM-based selection](#advanced-selection-by-quality-drm-or-profile)
|
|
to configure a device for each system. unshackle selects the right one per track
|
|
automatically.
|
|
|
|
### License both systems with `--all-drm`
|
|
|
|
By default a track licenses with one DRM system. `--all-drm` gets the content keys for
|
|
Widevine and for PlayReady on each track that offers both. Use it with `--skip-dl` and
|
|
`--export` to keep the content keys and the DRM init data of both systems. The two
|
|
systems can list different sets of KIDs, and unshackle keeps the content keys of both.
|
|
|
|
```shell
|
|
unshackle dl --skip-dl --all-drm --export EXAMPLE 81234567
|
|
```
|
|
|
|
- The `cdm` mapping must name a device for each system. A single device name, or `--cdm`,
|
|
stops the run with an error that names the missing system.
|
|
- The order of the `widevine` and `playready` config keys in the `cdm` entry sets the order
|
|
of the two systems, as in a run without the flag. Each track licenses first with the
|
|
system of the first config key.
|
|
- A Key Vault is read first, as in a run without the flag. A DRM system sends a challenge
|
|
only for a KID that no Key Vault and no earlier track of the run gave a content key
|
|
for. When the first system returns all the KIDs of the second, the second sends no
|
|
challenge.
|
|
- With `--cdm-only`, unshackle does not read the Key Vaults, and each DRM system sends one
|
|
challenge for a set of KIDs in a run.
|
|
- A service with a Widevine licence function only gets the PlayReady challenge through that
|
|
function. If its licence server refuses the challenge, that DRM system does not license.
|
|
- If one DRM system does not license, the title fails. With `--best-available` unshackle
|
|
shows a warning and continues, if the other system gave the content key of the track.
|
|
unshackle does not send the challenge again for the same KIDs in that run.
|
|
- A track gets its DRM systems from the manifest. For a DASH track or a direct URL,
|
|
unshackle also reads the init segment for a system that the manifest does not carry.
|
|
- With `--cdm-only`, the service gets a challenge from each DRM system for each set of
|
|
KIDs. A licence server that limits the request rate can answer `429`, and unshackle then
|
|
tries again.
|
|
|
|
!!! note "Where `--all-drm` licenses one system only"
|
|
- A service that gets a different manifest for each DRM system. unshackle shows a
|
|
warning one time when a track offers one system only.
|
|
- A service that fetched its playback for one DRM system and refuses the other. That
|
|
system does not license, so the title fails unless you use `--best-available`.
|
|
- An HLS playlist that changes its `EXT-X-KEY` entries part-way: only the first set
|
|
licenses with the second system.
|
|
- A remote session that the server CDM licenses, and `unshackle import`. unshackle
|
|
shows a warning and ignores the flag.
|
|
- A remote CDM that stores content keys can answer without a challenge to the service.
|
|
|
|
## Keys, vaults, and caching
|
|
|
|
Fetching a license is comparatively slow and, for some services, rate-limited.
|
|
unshackle therefore resolves each key ID in this order before ever contacting a
|
|
license server:
|
|
|
|
1. Keys already known for the current DRM object.
|
|
2. An in-process cache of content keys, for the current `dl` run.
|
|
3. Your configured [key vaults](vaults.md).
|
|
4. Only if still missing, a live license request.
|
|
|
|
After a successful license request, unshackle writes the recovered keys back to all
|
|
configured vaults, so future downloads can skip the license server. Two flags let you
|
|
control this trade-off on the `dl` command:
|
|
|
|
| Flag | Effect |
|
|
|------|--------|
|
|
| `--vaults-only` | Never send a license request; use only cached/vault keys. Fails if a content key is missing. |
|
|
| `--cdm-only` | Ignore vaults for sourcing keys and always perform a fresh license request. Over a `server_cdm` remote session the server still reads its own vaults; the flag only skips the client's decode check on the key it gets back. |
|
|
| `--cdm <name>` | Use the named CDM device for this run instead of the one the `cdm` config maps for the service. Over `--remote`, that device licenses the remote session, not the server CDM. |
|
|
|
|
See [key vaults](vaults.md) for how to configure vault backends.
|
|
|
|
## Debugging DRM issues
|
|
|
|
A few configuration toggles help when a title will not decrypt:
|
|
|
|
| Config key | Default | Effect |
|
|
|------------|---------|--------|
|
|
| `debug` | `false` | Global debug output. |
|
|
| `debug_keys` | `false` | Log decryption keys. |
|
|
| `debug_requests` | `false` | Log HTTP requests. |
|
|
|
|
```yaml title="unshackle.yaml"
|
|
debug_keys: true
|
|
debug_requests: true
|
|
```
|
|
|
|
!!! warning "Keys and paths in logs"
|
|
When you enable key logging, unshackle writes content keys (KID:key pairs) to the logs.
|
|
Treat those logs as sensitive. The unrelated `redact_paths` option (default
|
|
`true`) masks local directory paths in log output.
|
|
|
|
## See also
|
|
|
|
- [Downloading](downloading.md): the `dl` command and its DRM-related flags.
|
|
- [Key vaults](vaults.md): caching content keys across downloads.
|
|
- [Configuration](../reference/configuration/index.md): the full `unshackle.yaml` reference.
|
|
- [Dependencies](../getting-started/installation.md): installing shaka-packager and mp4decrypt.
|