Release v1.0.0

This commit is contained in:
hyugogirubato 2024-03-30 20:03:15 +01:00
parent 92e537510a
commit c2838dec0b
16 changed files with 15962 additions and 1 deletions

2
.gitignore vendored
View File

@ -157,4 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ .idea/

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# KeyDive: Widevine L3 Extractor for Android
KeyDive is a sophisticated Python script designed for the precise extraction of Widevine L3 DRM (Digital Rights Management) keys from Android devices. This tool leverages the capabilities of the Widevine CDM (Content Decryption Module) to facilitate the recovery of DRM keys, enabling a deeper understanding and analysis of the Widevine L3 DRM implementation across various Android SDK versions.
> **Warning**
>
> Support for Android 14+ (SDK > 33) is currently under development. Some features may not function as expected on these newer versions.
## Features
- Automated extraction of Widevine L3 DRM keys.
- Compatibility with a wide range of Android versions (SDK > 22), ensuring broad applicability.
- Seamless extraction process, yielding essential DRM components such as the `client_id.bin` for device identification and the `private_key.pem` for the RSA private key.
## Prerequisites
Before you begin, ensure you have the following prerequisites in place:
1. **ADB (Android Debug Bridge):** Make sure to install [ADB](https://developer.android.com/studio/command-line/adb) and include it in your system's PATH environment variable for easy command-line access.
2. **Frida-Server:** Install `frida-server` on your target Android device. This requires root access on the device. For installation instructions and downloads, visit the [official Frida documentation](https://frida.re/docs/installation/).
3. **Python Requirements:** KeyDive requires specific Python libraries to function correctly. Install them using the provided `requirements.txt` file:
```shell
pip install -r requirements.txt
```
## Installation
Follow these steps to set up KeyDive:
1. Ensure all prerequisites are met (see above).
2. Clone this repository to your local machine.
3. Navigate to the cloned directory and install the required Python dependencies as mentioned.
## Usage
To use KeyDive, follow these steps:
1. Launch the KeyDive script.
2. Play a DRM-protected video on the target device.
3. The script will automatically extract the Widevine L3 keys, saving them in the following format:
- `client_id.bin` - Contains device identification information.
- `private_key.pem` - Contains the RSA private key.
### Command-Line Options
```sh
usage: keydive.py [-h] [--device DEVICE]
Extract Widevine L3 keys from an Android device.
options:
-h, --help show this help message and exit
--device DEVICE Target Android device ID.
```
## Temporary Disabling L1 for L3 Extraction
Some manufacturers (e.g., Xiaomi) allow the use of L1 keyboxes even after unlocking the bootloader. In such cases, it's necessary to install a Magisk module called [liboemcrypto-disabler](https://github.com/Magisk-Modules-Repo/liboemcryptodisabler) to temporarily disable L1, thereby facilitating L3 key extraction.
## Credits
Special thanks to the original developers and contributors who have made KeyDive possible. This tool is the culmination of collaborative efforts, research, and a deep understanding of DRM technologies.
## Disclaimer
KeyDive is intended for educational and research purposes only. The use of this tool in unauthorized testing of protected content is strictly prohibited. Please ensure you have permission before proceeding with DRM key extraction.
---
By using KeyDive, you acknowledge and agree to the terms of use and disclaimer stated above.

35
docs/README.md Normal file
View File

@ -0,0 +1,35 @@
# Packages
This document provides an overview of the external libraries, tools, and applications utilized within the KeyDive project. Each package plays a crucial role in enabling the project to efficiently extract Widevine L3 keys from Android devices for educational and research purposes.
## Tools and Libraries
### [rootAVD](https://gitlab.com/newbit/rootAVD)
A tool designed to root Android Virtual Devices (AVDs). It enables users to gain superuser privileges on their AVDs, essential for accessing and modifying system-level files and settings that are otherwise restricted.
### [DRM Info](https://apkcombo.com/drm-info/com.androidfung.drminfo/download/phone-1.1.9.220313-apk)
An Android application providing detailed information about the device's Digital Rights Management (DRM) modules, including Widevine. Useful for verifying the DRM support level (L1, L2, L3) on the target device.
### [Root Explorer](https://apkcombo.com/root-explorer/com.speedsoftware.rootexplorer/)
A file manager for root users, offering access to the entire Android file system, including typically hidden or inaccessible data folders.
### [Firefox](https://apkcombo.com/fr/firefox/org.mozilla.firefox/)
A free and open-source web browser for Android, used for downloading files, testing DRM content playback, and other web-related tasks during research.
### [liboemcrypto Disabler](https://github.com/Magisk-Modules-Repo/liboemcryptodisabler)
A Magisk module that disables the OEMCrypto service, responsible for L1 DRM protection, forcing devices to fallback to L3 protection and enabling the extraction of L3 keys.
### [MagiskFrida](https://github.com/ViRb3/magisk-frida)
Allows Frida, a dynamic instrumentation toolkit, to run as a Magisk module, ideal for environments where adb access is limited or not possible.
### [Frida](https://github.com/frida/frida/releases)
A dynamic code instrumentation toolkit for injecting JavaScript or your own library into native apps on Android and other platforms.
### [adb (Android Debug Bridge)](https://developer.android.com/tools/adb)
A command-line tool for communicating with a device, facilitating actions such as app installation and debugging, and providing access to a Unix shell for running various commands.
### [Ghidra](https://github.com/NationalSecurityAgency/ghidra)
A software reverse engineering (SRE) framework developed by the National Security Agency (NSA) that helps analyze malicious code and malware, and understand their functionality. Ghidra is essential for decompiling and analyzing the binaries and libraries involved in the DRM mechanisms, offering insights into how they operate and can be interacted with.
## Usage
The combination of these tools provides a comprehensive toolkit for DRM research, allowing for the exploration of digital content protection mechanisms on Android devices. Each tool has been selected for its ability to contribute to the setup, execution, or support of the KeyDive project, enabling detailed analysis and extraction of digital rights management keys.

17
docs/shell.sh Normal file
View File

@ -0,0 +1,17 @@
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
alias logcat='logcat -v color'
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias ipa='ip -c a'
alias rm='rm -rf'
tree() {
path=${1:-.}
find ${path} -print | sort | sed 's;[^/]*/;|---;g;s;---|; |;g'
}

File diff suppressed because it is too large Load Diff

4
extractor/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from .cdm import *
from .vendor import *
__version__ = '1.0.0'

149
extractor/cdm.py Normal file
View File

@ -0,0 +1,149 @@
import logging
import re
import subprocess
from pathlib import Path
import frida
from _frida import Process
from frida.core import Device, Session, Script, RPCException
from Cryptodome.PublicKey import RSA
from extractor.license_protocol_pb2 import SignedMessage, LicenseRequest, ClientIdentification, DrmCertificate, SignedDrmCertificate
from extractor.vendor import Vendor
SCRIPT_PATH = Path(__file__).parent / 'script.js'
class Cdm:
"""
Manages the capture and processing of DRM keys from a specified device using Frida to inject custom hooks.
"""
def __init__(self, device: str = None):
self.logger = logging.getLogger('Cdm')
self.running = True
self.keys = {}
self.device: Device = frida.get_device(id=device, timeout=5) if device else frida.get_usb_device(timeout=5)
self.logger.info('Device: %s (%s)', self.device.name, self.device.id)
# Fetch and log device properties
self.properties = self._fetch_device_properties()
self.sdk_api = self.properties['ro.build.version.sdk']
self.logger.info('SDK API: %s', self.sdk_api)
self.logger.info('ABI CPU: %s', self.properties['ro.product.cpu.abi'])
# Determine vendor based on SDK API
self.vendor = Vendor.from_sdk_api(self.sdk_api)
self.script: str = self._prepare_hook_script()
def _fetch_device_properties(self) -> dict:
"""
Retrieves system properties from the connected device using ADB shell commands.
"""
# https://source.android.com/docs/core/architecture/configuration/add-system-properties?#shell-commands
properties = {}
for line in subprocess.getoutput(f'adb -s "{self.device.id}" shell getprop').splitlines():
match = re.match(r'\[(.*?)\]: \[(.*?)\]', line)
if match:
key, value = match.groups()
# Attempt to cast numeric and boolean values to appropriate types
try:
value = int(value)
except ValueError:
if value.lower() in ('true', 'false'):
value = value.lower() == 'true'
properties[key] = value
return properties
def _prepare_hook_script(self) -> str:
"""
Prepares and returns the hook script with the SDK API version replaced.
"""
script_content = SCRIPT_PATH.read_text(encoding='utf-8')
return script_content.replace("'${SDK_API}'", str(self.sdk_api))
def _process_message(self, message: dict, data: bytes) -> None:
"""
Handles messages received from the Frida script.
"""
logger = logging.getLogger('Script')
level = message.get('payload')
if isinstance(level, int):
# Process logging messages from Frida script
logger.log(level=level, msg=data.decode('utf-8'))
if level in (logging.FATAL, logging.CRITICAL):
self.running = False
elif level == 'device_info':
if data:
self._extract_device_info(data)
else:
logger.critical('No data for device info, invalid argument position')
self.running = False
elif level == 'private_key':
self._extract_private_key(data)
def _extract_private_key(self, data: bytes) -> None:
"""
Extracts and stores the private key from the provided data.
"""
key = RSA.import_key(data)
key_id = key.n
if key_id not in self.keys:
self.keys[key_id] = key
self.logger.debug('Retrieved key: \n\n%s\n', key.exportKey('PEM').decode('utf-8'))
def _extract_device_info(self, data: bytes) -> None:
"""
Extracts device information and associated private keys, storing them to disk.
"""
# https://github.com/devine-dl/pywidevine
signed_message = SignedMessage()
signed_message.ParseFromString(data)
license_request = LicenseRequest()
license_request.ParseFromString(signed_message.msg)
client_id: ClientIdentification = license_request.client_id
signed_drm_certificate = SignedDrmCertificate()
drm_certificate = DrmCertificate()
signed_drm_certificate.ParseFromString(client_id.token)
drm_certificate.ParseFromString(signed_drm_certificate.drm_certificate)
public_key = drm_certificate.public_key
key = RSA.importKey(public_key)
key_id = key.n
private_key = self.keys.get(key_id)
if private_key:
path = Path() / 'device' / self.device.name / 'private_keys' / str(drm_certificate.system_id) / str(key_id)[:10]
path.mkdir(parents=True, exist_ok=True)
path_client_id = path / 'client_id.bin'
path_private_key = path / 'private_key.pem'
path_client_id.write_bytes(data=client_id.SerializeToString())
path_private_key.write_bytes(data=private_key.exportKey('PEM'))
self.logger.info('Dumped client ID: %s', path_client_id)
self.logger.info('Dumped private key: %s', path_private_key)
self.running = False
else:
self.logger.warning('Failed to intercept the private key')
def hook_process(self, process: Process) -> bool:
"""
Hooks into the specified process to intercept DRM keys.
"""
session: Session = self.device.attach(process.name)
script: Script = session.create_script(self.script)
script.on('message', self._process_message)
script.load()
try:
library_info = script.exports_sync.getlibrary(self.vendor.library)
self.logger.info('Library: %s (%s)', library_info['name'], library_info['path'])
return script.exports_sync.hooklibrary(library_info['name'])
except RPCException:
return False

View File

@ -0,0 +1,752 @@
syntax = "proto2";
package pywidevine_license_protocol;
// need this if we are using libprotobuf-cpp-2.3.0-lite
option optimize_for = LITE_RUNTIME;
option java_package = "com.rlaphoenix.pywidevine.protos";
enum LicenseType {
STREAMING = 1;
OFFLINE = 2;
// License type decision is left to provider.
AUTOMATIC = 3;
}
enum PlatformVerificationStatus {
// The platform is not verified.
PLATFORM_UNVERIFIED = 0;
// Tampering detected on the platform.
PLATFORM_TAMPERED = 1;
// The platform has been verified by means of software.
PLATFORM_SOFTWARE_VERIFIED = 2;
// The platform has been verified by means of hardware (e.g. secure boot).
PLATFORM_HARDWARE_VERIFIED = 3;
// Platform verification was not performed.
PLATFORM_NO_VERIFICATION = 4;
// Platform and secure storage capability have been verified by means of
// software.
PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED = 5;
}
// LicenseIdentification is propagated from LicenseRequest to License,
// incrementing version with each iteration.
message LicenseIdentification {
optional bytes request_id = 1;
optional bytes session_id = 2;
optional bytes purchase_id = 3;
optional LicenseType type = 4;
optional int32 version = 5;
optional bytes provider_session_token = 6;
}
message License {
message Policy {
// Indicates that playback of the content is allowed.
optional bool can_play = 1 [default = false];
// Indicates that the license may be persisted to non-volatile
// storage for offline use.
optional bool can_persist = 2 [default = false];
// Indicates that renewal of this license is allowed.
optional bool can_renew = 3 [default = false];
// For the |*duration*| fields, playback must halt when
// license_start_time (seconds since the epoch (UTC)) +
// license_duration_seconds is exceeded. A value of 0
// indicates that there is no limit to the duration.
// Indicates the rental window.
optional int64 rental_duration_seconds = 4 [default = 0];
// Indicates the viewing window, once playback has begun.
optional int64 playback_duration_seconds = 5 [default = 0];
// Indicates the time window for this specific license.
optional int64 license_duration_seconds = 6 [default = 0];
// The |renewal*| fields only apply if |can_renew| is true.
// The window of time, in which playback is allowed to continue while
// renewal is attempted, yet unsuccessful due to backend problems with
// the license server.
optional int64 renewal_recovery_duration_seconds = 7 [default = 0];
// All renewal requests for this license shall be directed to the
// specified URL.
optional string renewal_server_url = 8;
// How many seconds after license_start_time, before renewal is first
// attempted.
optional int64 renewal_delay_seconds = 9 [default = 0];
// Specifies the delay in seconds between subsequent license
// renewal requests, in case of failure.
optional int64 renewal_retry_interval_seconds = 10 [default = 0];
// Indicates that the license shall be sent for renewal when usage is
// started.
optional bool renew_with_usage = 11 [default = false];
// Indicates to client that license renewal and release requests ought to
// include ClientIdentification (client_id).
optional bool always_include_client_id = 12 [default = false];
// Duration of grace period before playback_duration_seconds (short window)
// goes into effect. Optional.
optional int64 play_start_grace_period_seconds = 13 [default = 0];
// Enables "soft enforcement" of playback_duration_seconds, letting the user
// finish playback even if short window expires. Optional.
optional bool soft_enforce_playback_duration = 14 [default = false];
// Enables "soft enforcement" of rental_duration_seconds. Initial playback
// must always start before rental duration expires. In order to allow
// subsequent playbacks to start after the rental duration expires,
// soft_enforce_playback_duration must be true. Otherwise, subsequent
// playbacks will not be allowed once rental duration expires. Optional.
optional bool soft_enforce_rental_duration = 15 [default = true];
}
message KeyContainer {
enum KeyType {
SIGNING = 1; // Exactly one key of this type must appear.
CONTENT = 2; // Content key.
KEY_CONTROL = 3; // Key control block for license renewals. No key.
OPERATOR_SESSION = 4; // wrapped keys for auxiliary crypto operations.
ENTITLEMENT = 5; // Entitlement keys.
OEM_CONTENT = 6; // Partner-specific content key.
}
// The SecurityLevel enumeration allows the server to communicate the level
// of robustness required by the client, in order to use the key.
enum SecurityLevel {
// Software-based whitebox crypto is required.
SW_SECURE_CRYPTO = 1;
// Software crypto and an obfuscated decoder is required.
SW_SECURE_DECODE = 2;
// The key material and crypto operations must be performed within a
// hardware backed trusted execution environment.
HW_SECURE_CRYPTO = 3;
// The crypto and decoding of content must be performed within a hardware
// backed trusted execution environment.
HW_SECURE_DECODE = 4;
// The crypto, decoding and all handling of the media (compressed and
// uncompressed) must be handled within a hardware backed trusted
// execution environment.
HW_SECURE_ALL = 5;
}
message KeyControl {
// |key_control| is documented in:
// Widevine Modular DRM Security Integration Guide for CENC
// If present, the key control must be communicated to the secure
// environment prior to any usage. This message is automatically generated
// by the Widevine License Server SDK.
optional bytes key_control_block = 1;
optional bytes iv = 2;
}
message OutputProtection {
// Indicates whether HDCP is required on digital outputs, and which
// version should be used.
enum HDCP {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
HDCP_V2_3 = 5;
HDCP_NO_DIGITAL_OUTPUT = 0xff;
}
optional HDCP hdcp = 1 [default = HDCP_NONE];
// Indicate the CGMS setting to be inserted on analog output.
enum CGMS {
CGMS_NONE = 42;
COPY_FREE = 0;
COPY_ONCE = 2;
COPY_NEVER = 3;
}
optional CGMS cgms_flags = 2 [default = CGMS_NONE];
enum HdcpSrmRule {
HDCP_SRM_RULE_NONE = 0;
// In 'required_protection', this means most current SRM is required.
// Update the SRM on the device. If update cannot happen,
// do not allow the key.
// In 'requested_protection', this means most current SRM is requested.
// Update the SRM on the device. If update cannot happen,
// allow use of the key anyway.
CURRENT_SRM = 1;
}
optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE];
// Optional requirement to indicate analog output is not allowed.
optional bool disable_analog_output = 4 [default = false];
// Optional requirement to indicate digital output is not allowed.
optional bool disable_digital_output = 5 [default = false];
}
message VideoResolutionConstraint {
// Minimum and maximum video resolutions in the range (height x width).
optional uint32 min_resolution_pixels = 1;
optional uint32 max_resolution_pixels = 2;
// Optional output protection requirements for this range. If not
// specified, the OutputProtection in the KeyContainer applies.
optional OutputProtection required_protection = 3;
}
message OperatorSessionKeyPermissions {
// Permissions/key usage flags for operator service keys
// (type = OPERATOR_SESSION).
optional bool allow_encrypt = 1 [default = false];
optional bool allow_decrypt = 2 [default = false];
optional bool allow_sign = 3 [default = false];
optional bool allow_signature_verify = 4 [default = false];
}
optional bytes id = 1;
optional bytes iv = 2;
optional bytes key = 3;
optional KeyType type = 4;
optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO];
optional OutputProtection required_protection = 6;
// NOTE: Use of requested_protection is not recommended as it is only
// supported on a small number of platforms.
optional OutputProtection requested_protection = 7;
optional KeyControl key_control = 8;
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
// Optional video resolution constraints. If the video resolution of the
// content being decrypted/decoded falls within one of the specified ranges,
// the optional required_protections may be applied. Otherwise an error will
// be reported.
// NOTE: Use of this feature is not recommended, as it is only supported on
// a small number of platforms.
repeated VideoResolutionConstraint video_resolution_constraints = 10;
// Optional flag to indicate the key must only be used if the client
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
// Optional not limited to commonly known track types such as SD, HD.
// It can be some provider defined label to identify the track.
optional string track_label = 12;
}
optional LicenseIdentification id = 1;
optional Policy policy = 2;
repeated KeyContainer key = 3;
// Time of the request in seconds (UTC) as set in
// LicenseRequest.request_time. If this time is not set in the request,
// the local time at the license service is used in this field.
optional int64 license_start_time = 4;
optional bool remote_attestation_verified = 5 [default = false];
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 6;
// 4cc code specifying the CENC protection scheme as defined in the CENC 3.0
// specification. Propagated from Widevine PSSH box. Optional.
optional uint32 protection_scheme = 7;
// 8 byte verification field "HDCPDATA" followed by unsigned 32 bit minimum
// HDCP SRM version (whether the version is for HDCP1 SRM or HDCP2 SRM
// depends on client max_hdcp_version).
// Additional details can be found in Widevine Modular DRM Security
// Integration Guide for CENC.
optional bytes srm_requirement = 8;
// If present this contains a signed SRM file (either HDCP1 SRM or HDCP2 SRM
// depending on client max_hdcp_version) that should be installed on the
// client device.
optional bytes srm_update = 9;
// Indicates the status of any type of platform verification performed by the
// server.
optional PlatformVerificationStatus platform_verification_status = 10
[default = PLATFORM_NO_VERIFICATION];
// IDs of the groups for which keys are delivered in this license, if any.
repeated bytes group_ids = 11;
}
enum ProtocolVersion {
VERSION_2_0 = 20;
VERSION_2_1 = 21;
VERSION_2_2 = 22;
}
message LicenseRequest {
message ContentIdentification {
message WidevinePsshData {
repeated bytes pssh_data = 1;
optional LicenseType license_type = 2;
optional bytes request_id = 3; // Opaque, client-specified.
}
message WebmKeyId {
optional bytes header = 1;
optional LicenseType license_type = 2;
optional bytes request_id = 3; // Opaque, client-specified.
}
message ExistingLicense {
optional LicenseIdentification license_id = 1;
optional int64 seconds_since_started = 2;
optional int64 seconds_since_last_played = 3;
optional bytes session_usage_table_entry = 4;
}
message InitData {
enum InitDataType {
CENC = 1;
WEBM = 2;
}
optional InitDataType init_data_type = 1 [default = CENC];
optional bytes init_data = 2;
optional LicenseType license_type = 3;
optional bytes request_id = 4;
}
oneof content_id_variant {
// Exactly one of these must be present.
WidevinePsshData widevine_pssh_data = 1;
WebmKeyId webm_key_id = 2;
ExistingLicense existing_license = 3;
InitData init_data = 4;
}
}
enum RequestType {
NEW = 1;
RENEWAL = 2;
RELEASE = 3;
}
// The client_id provides information authenticating the calling device. It
// contains the Widevine keybox token that was installed on the device at the
// factory. This field or encrypted_client_id below is required for a valid
// license request, but both should never be present in the same request.
optional ClientIdentification client_id = 1;
optional ContentIdentification content_id = 2;
optional RequestType type = 3;
// Time of the request in seconds (UTC) as set by the client.
optional int64 request_time = 4;
// Old-style decimal-encoded string key control nonce.
optional bytes key_control_nonce_deprecated = 5;
optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0];
// New-style uint32 key control nonce, please use instead of
// key_control_nonce_deprecated.
optional uint32 key_control_nonce = 7;
// Encrypted ClientIdentification message, used for privacy purposes.
optional EncryptedClientIdentification encrypted_client_id = 8;
}
message MetricData {
enum MetricType {
// The time spent in the 'stage', specified in microseconds.
LATENCY = 1;
// The UNIX epoch timestamp at which the 'stage' was first accessed in
// microseconds.
TIMESTAMP = 2;
}
message TypeValue {
optional MetricType type = 1;
// The value associated with 'type'. For example if type == LATENCY, the
// value would be the time in microseconds spent in this 'stage'.
optional int64 value = 2 [default = 0];
}
// 'stage' that is currently processing the SignedMessage. Required.
optional string stage_name = 1;
// metric and associated value.
repeated TypeValue metric_data = 2;
}
message VersionInfo {
// License SDK version reported by the Widevine License SDK. This field
// is populated automatically by the SDK.
optional string license_sdk_version = 1;
// Version of the service hosting the license SDK. This field is optional.
// It may be provided by the hosting service.
optional string license_service_version = 2;
}
message SignedMessage {
enum MessageType {
LICENSE_REQUEST = 1;
LICENSE = 2;
ERROR_RESPONSE = 3;
SERVICE_CERTIFICATE_REQUEST = 4;
SERVICE_CERTIFICATE = 5;
SUB_LICENSE = 6;
CAS_LICENSE_REQUEST = 7;
CAS_LICENSE = 8;
EXTERNAL_LICENSE_REQUEST = 9;
EXTERNAL_LICENSE = 10;
}
enum SessionKeyType {
UNDEFINED = 0;
WRAPPED_AES_KEY = 1;
EPHERMERAL_ECC_PUBLIC_KEY = 2;
}
optional MessageType type = 1;
optional bytes msg = 2;
// Required field that contains the signature of the bytes of msg.
// For license requests, the signing algorithm is determined by the
// certificate contained in the request.
// For license responses, the signing algorithm is HMAC with signing key based
// on |session_key|.
optional bytes signature = 3;
// If populated, the contents of this field will be signaled by the
// |session_key_type| type. If the |session_key_type| is WRAPPED_AES_KEY the
// key is the bytes of an encrypted AES key. If the |session_key_type| is
// EPHERMERAL_ECC_PUBLIC_KEY the field contains the bytes of an RFC5208 ASN1
// serialized ECC public key.
optional bytes session_key = 4;
// Remote attestation data which will be present in the initial license
// request for ChromeOS client devices operating in verified mode. Remote
// attestation challenge data is |msg| field above. Optional.
optional bytes remote_attestation = 5;
repeated MetricData metric_data = 6;
// Version information from the SDK and license service. This information is
// provided in the license response.
optional VersionInfo service_version_info = 7;
// Optional field that contains the algorithm type used to generate the
// session_key and signature in a LICENSE message.
optional SessionKeyType session_key_type = 8 [default = WRAPPED_AES_KEY];
// The core message is the simple serialization of fields used by OEMCrypto.
// This field was introduced in OEMCrypto API v16.
optional bytes oemcrypto_core_message = 9;
}
enum HashAlgorithmProto {
// Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms
// and SHA_1 shall be used otherwise.
HASH_ALGORITHM_UNSPECIFIED = 0;
HASH_ALGORITHM_SHA_1 = 1;
HASH_ALGORITHM_SHA_256 = 2;
HASH_ALGORITHM_SHA_384 = 3;
}
// ClientIdentification message used to authenticate the client device.
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
DRM_DEVICE_CERTIFICATE = 1;
REMOTE_ATTESTATION_CERTIFICATE = 2;
OEM_DEVICE_CERTIFICATE = 3;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
// Capabilities which not all clients may support. Used for the license
// exchange protocol only.
message ClientCapabilities {
enum HdcpVersion {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
HDCP_V2_3 = 5;
HDCP_NO_DIGITAL_OUTPUT = 0xff;
}
enum CertificateKeyType {
RSA_2048 = 0;
RSA_3072 = 1;
ECC_SECP256R1 = 2;
ECC_SECP384R1 = 3;
ECC_SECP521R1 = 4;
}
enum AnalogOutputCapabilities {
ANALOG_OUTPUT_UNKNOWN = 0;
ANALOG_OUTPUT_NONE = 1;
ANALOG_OUTPUT_SUPPORTED = 2;
ANALOG_OUTPUT_SUPPORTS_CGMS_A = 3;
}
optional bool client_token = 1 [default = false];
optional bool session_token = 2 [default = false];
optional bool video_resolution_constraints = 3 [default = false];
optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE];
optional uint32 oem_crypto_api_version = 5;
// Client has hardware support for protecting the usage table, such as
// storing the generation number in secure memory. For Details, see:
// Widevine Modular DRM Security Integration Guide for CENC
optional bool anti_rollback_usage_table = 6 [default = false];
// The client shall report |srm_version| if available.
optional uint32 srm_version = 7;
// A device may have SRM data, and report a version, but may not be capable
// of updating SRM data.
optional bool can_update_srm = 8 [default = false];
repeated CertificateKeyType supported_certificate_key_type = 9;
optional AnalogOutputCapabilities analog_output_capabilities = 10
[default = ANALOG_OUTPUT_UNKNOWN];
optional bool can_disable_analog_output = 11 [default = false];
// Clients can indicate a performance level supported by OEMCrypto.
// This will allow applications and providers to choose an appropriate
// quality of content to serve. Currently defined tiers are
// 1 (low), 2 (medium) and 3 (high). Any other value indicates that
// the resource rating is unavailable or reporting erroneous values
// for that device. For details see,
// Widevine Modular DRM Security Integration Guide for CENC
optional uint32 resource_rating_tier = 12 [default = 0];
}
message ClientCredentials {
optional TokenType type = 1 [default = KEYBOX];
optional bytes token = 2;
}
// Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required.
optional bytes token = 2;
// Optional client information name/value pairs.
repeated NameValue client_info = 3;
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 4;
// Number of licenses received by the client to which the token above belongs.
// Only present if client_token is specified.
optional uint32 license_counter = 5;
// List of non-baseline client capabilities.
optional ClientCapabilities client_capabilities = 6;
// Serialized VmpData message. Optional.
optional bytes vmp_data = 7;
// Optional field that may contain additional provisioning credentials.
repeated ClientCredentials device_credentials = 8;
}
// EncryptedClientIdentification message used to hold ClientIdentification
// messages encrypted for privacy purposes.
message EncryptedClientIdentification {
// Provider ID for which the ClientIdentifcation is encrypted (owner of
// service certificate).
optional string provider_id = 1;
// Serial number for the service certificate for which ClientIdentification is
// encrypted.
optional bytes service_certificate_serial_number = 2;
// Serialized ClientIdentification message, encrypted with the privacy key
// using AES-128-CBC with PKCS#5 padding.
optional bytes encrypted_client_id = 3;
// Initialization vector needed to decrypt encrypted_client_id.
optional bytes encrypted_client_id_iv = 4;
// AES-128 privacy key, encrypted with the service public key using RSA-OAEP.
optional bytes encrypted_privacy_key = 5;
}
// DRM certificate definition for user devices, intermediate, service, and root
// certificates.
message DrmCertificate {
enum Type {
ROOT = 0; // ProtoBestPractices: ignore.
DEVICE_MODEL = 1;
DEVICE = 2;
SERVICE = 3;
PROVISIONER = 4;
}
enum ServiceType {
UNKNOWN_SERVICE_TYPE = 0;
LICENSE_SERVER_SDK = 1;
LICENSE_SERVER_PROXY_SDK = 2;
PROVISIONING_SDK = 3;
CAS_PROXY_SDK = 4;
}
enum Algorithm {
UNKNOWN_ALGORITHM = 0;
RSA = 1;
ECC_SECP256R1 = 2;
ECC_SECP384R1 = 3;
ECC_SECP521R1 = 4;
}
message EncryptionKey {
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
optional bytes public_key = 1;
// Required. The algorithm field contains the curve used to create the
// |public_key| if algorithm is one of the ECC types.
// The |algorithm| is used for both to determine the if the certificate is
// ECC or RSA. The |algorithm| also specifies the parameters that were used
// to create |public_key| and are used to create an ephemeral session key.
optional Algorithm algorithm = 2 [default = RSA];
}
// Type of certificate. Required.
optional Type type = 1;
// 128-bit globally unique serial number of certificate.
// Value is 0 for root certificate. Required.
optional bytes serial_number = 2;
// POSIX time, in seconds, when the certificate was created. Required.
optional uint32 creation_time_seconds = 3;
// POSIX time, in seconds, when the certificate should expire. Value of zero
// denotes indefinite expiry time. For more information on limited lifespan
// DRM certificates see (go/limited-lifespan-drm-certificates).
optional uint32 expiration_time_seconds = 12;
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
optional bytes public_key = 4;
// Widevine system ID for the device. Required for intermediate and
// user device certificates.
optional uint32 system_id = 5;
// Deprecated field, which used to indicate whether the device was a test
// (non-production) device. The test_device field in ProvisionedDeviceInfo
// below should be observed instead.
optional bool test_device_deprecated = 6 [deprecated = true];
// Service identifier (web origin) for the provider which owns the
// certificate. Required for service and provisioner certificates.
optional string provider_id = 7;
// This field is used only when type = SERVICE to specify which SDK uses
// service certificate. This repeated field is treated as a set. A certificate
// may be used for the specified service SDK if the appropriate ServiceType
// is specified in this field.
repeated ServiceType service_types = 8;
// Required. The algorithm field contains the curve used to create the
// |public_key| if algorithm is one of the ECC types.
// The |algorithm| is used for both to determine the if the certificate is ECC
// or RSA. The |algorithm| also specifies the parameters that were used to
// create |public_key| and are used to create an ephemeral session key.
optional Algorithm algorithm = 9 [default = RSA];
// Optional. May be present in DEVICE certificate types. This is the root
// of trust identifier that holds an encrypted value that identifies the
// keybox or other root of trust that was used to provision a DEVICE drm
// certificate.
optional bytes rot_id = 10;
// Optional. May be present in devices that explicitly support dual keys. When
// present the |public_key| is used for verification of received license
// request messages.
optional EncryptionKey encryption_key = 11;
}
// DrmCertificate signed by a higher (CA) DRM certificate.
message SignedDrmCertificate {
// Serialized certificate. Required.
optional bytes drm_certificate = 1;
// Signature of certificate. Signed with root or intermediate
// certificate specified below. Required.
optional bytes signature = 2;
// SignedDrmCertificate used to sign this certificate.
optional SignedDrmCertificate signer = 3;
// Optional field that indicates the hash algorithm used in signature scheme.
optional HashAlgorithmProto hash_algorithm = 4;
}
message WidevinePsshData {
enum Type {
SINGLE = 0; // Single PSSH to be used to retrieve content keys.
ENTITLEMENT = 1; // Primary PSSH used to retrieve entitlement keys.
ENTITLED_KEY = 2; // Secondary PSSH containing entitled key(s).
}
message EntitledKey {
// ID of entitlement key used for wrapping |key|.
optional bytes entitlement_key_id = 1;
// ID of the entitled key.
optional bytes key_id = 2;
// Wrapped key. Required.
optional bytes key = 3;
// IV used for wrapping |key|. Required.
optional bytes iv = 4;
// Size of entitlement key used for wrapping |key|.
optional uint32 entitlement_key_size_bytes = 5 [default = 32];
}
// Entitlement or content key IDs. Can onnly present in SINGLE or ENTITLEMENT
// PSSHs. May be repeated to facilitate delivery of multiple keys in a
// single license. Cannot be used in conjunction with content_id or
// group_ids, which are the preferred mechanism.
repeated bytes key_ids = 2;
// Content identifier which may map to multiple entitlement or content key
// IDs to facilitate the delivery of multiple keys in a single license.
// Cannot be present in conjunction with key_ids, but if used must be in all
// PSSHs.
optional bytes content_id = 4;
// Crypto period index, for media using key rotation. Always corresponds to
// The content key period. This means that if using entitlement licensing
// the ENTITLED_KEY PSSHs will have sequential crypto_period_index's, whereas
// the ENTITELEMENT PSSHs will have gaps in the sequence. Required if doing
// key rotation.
optional uint32 crypto_period_index = 7;
// Protection scheme identifying the encryption algorithm. The protection
// scheme is represented as a uint32 value. The uint32 contains 4 bytes each
// representing a single ascii character in one of the 4CC protection scheme
// values. To be deprecated in favor of signaling from content.
// 'cenc' (AES-CTR) protection_scheme = 0x63656E63,
// 'cbc1' (AES-CBC) protection_scheme = 0x63626331,
// 'cens' (AES-CTR pattern encryption) protection_scheme = 0x63656E73,
// 'cbcs' (AES-CBC pattern encryption) protection_scheme = 0x63626373.
optional uint32 protection_scheme = 9;
// Optional. For media using key rotation, this represents the duration
// of each crypto period in seconds.
optional uint32 crypto_period_seconds = 10;
// Type of PSSH. Required if not SINGLE.
optional Type type = 11 [default = SINGLE];
// Key sequence for Widevine-managed keys. Optional.
optional uint32 key_sequence = 12;
// Group identifiers for all groups to which the content belongs. This can
// be used to deliver licenses to unlock multiple titles / channels.
// Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, and
// not in conjunction with key_ids.
repeated bytes group_ids = 13;
// Copy/copies of the content key used to decrypt the media stream in which
// the PSSH box is embedded, each wrapped with a different entitlement key.
// May also contain sub-licenses to support devices with OEMCrypto 13 or
// older. May be repeated if using group entitlement keys. Present only in
// PSSHs of type ENTITLED_KEY.
repeated EntitledKey entitled_keys = 14;
// Video feature identifier, which is used in conjunction with |content_id|
// to determine the set of keys to be returned in the license. Cannot be
// present in conjunction with |key_ids|.
// Current values are "HDR".
optional string video_feature = 15;
//////////////////////////// Deprecated Fields ////////////////////////////
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
optional Algorithm algorithm = 1 [deprecated = true];
// Content provider name.
optional string provider = 3 [deprecated = true];
// Track type. Acceptable values are SD, HD and AUDIO. Used to
// differentiate content keys used by an asset.
optional string track_type = 5 [deprecated = true];
// The name of a registered policy to be used for this asset.
optional string policy = 6 [deprecated = true];
// Optional protected context for group content. The grouped_license is a
// serialized SignedMessage.
optional bytes grouped_license = 8 [deprecated = true];
}
// File Hashes for Verified Media Path (VMP) support.
message FileHashes {
message Signature {
optional string filename = 1;
optional bool test_signing = 2; //0 - release, 1 - testing
optional bytes SHA512Hash = 3;
optional bool main_exe = 4; //0 for dlls, 1 for exe, this is field 3 in file
optional bytes signature = 5;
}
optional bytes signer = 1;
repeated Signature signatures = 2;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,607 @@
# mypy: ignore-errors
from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
AUTOMATIC: LicenseType
DESCRIPTOR: _descriptor.FileDescriptor
HASH_ALGORITHM_SHA_1: HashAlgorithmProto
HASH_ALGORITHM_SHA_256: HashAlgorithmProto
HASH_ALGORITHM_SHA_384: HashAlgorithmProto
HASH_ALGORITHM_UNSPECIFIED: HashAlgorithmProto
OFFLINE: LicenseType
PLATFORM_HARDWARE_VERIFIED: PlatformVerificationStatus
PLATFORM_NO_VERIFICATION: PlatformVerificationStatus
PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED: PlatformVerificationStatus
PLATFORM_SOFTWARE_VERIFIED: PlatformVerificationStatus
PLATFORM_TAMPERED: PlatformVerificationStatus
PLATFORM_UNVERIFIED: PlatformVerificationStatus
STREAMING: LicenseType
VERSION_2_0: ProtocolVersion
VERSION_2_1: ProtocolVersion
VERSION_2_2: ProtocolVersion
class ClientIdentification(_message.Message):
__slots__ = ["client_capabilities", "client_info", "device_credentials", "license_counter", "provider_client_token", "token", "type", "vmp_data"]
class TokenType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class ClientCapabilities(_message.Message):
__slots__ = ["analog_output_capabilities", "anti_rollback_usage_table", "can_disable_analog_output", "can_update_srm", "client_token", "max_hdcp_version", "oem_crypto_api_version", "resource_rating_tier", "session_token", "srm_version", "supported_certificate_key_type", "video_resolution_constraints"]
class AnalogOutputCapabilities(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class CertificateKeyType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class HdcpVersion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
ANALOG_OUTPUT_CAPABILITIES_FIELD_NUMBER: _ClassVar[int]
ANALOG_OUTPUT_NONE: ClientIdentification.ClientCapabilities.AnalogOutputCapabilities
ANALOG_OUTPUT_SUPPORTED: ClientIdentification.ClientCapabilities.AnalogOutputCapabilities
ANALOG_OUTPUT_SUPPORTS_CGMS_A: ClientIdentification.ClientCapabilities.AnalogOutputCapabilities
ANALOG_OUTPUT_UNKNOWN: ClientIdentification.ClientCapabilities.AnalogOutputCapabilities
ANTI_ROLLBACK_USAGE_TABLE_FIELD_NUMBER: _ClassVar[int]
CAN_DISABLE_ANALOG_OUTPUT_FIELD_NUMBER: _ClassVar[int]
CAN_UPDATE_SRM_FIELD_NUMBER: _ClassVar[int]
CLIENT_TOKEN_FIELD_NUMBER: _ClassVar[int]
ECC_SECP256R1: ClientIdentification.ClientCapabilities.CertificateKeyType
ECC_SECP384R1: ClientIdentification.ClientCapabilities.CertificateKeyType
ECC_SECP521R1: ClientIdentification.ClientCapabilities.CertificateKeyType
HDCP_NONE: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_NO_DIGITAL_OUTPUT: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_V1: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_V2: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_V2_1: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_V2_2: ClientIdentification.ClientCapabilities.HdcpVersion
HDCP_V2_3: ClientIdentification.ClientCapabilities.HdcpVersion
MAX_HDCP_VERSION_FIELD_NUMBER: _ClassVar[int]
OEM_CRYPTO_API_VERSION_FIELD_NUMBER: _ClassVar[int]
RESOURCE_RATING_TIER_FIELD_NUMBER: _ClassVar[int]
RSA_2048: ClientIdentification.ClientCapabilities.CertificateKeyType
RSA_3072: ClientIdentification.ClientCapabilities.CertificateKeyType
SESSION_TOKEN_FIELD_NUMBER: _ClassVar[int]
SRM_VERSION_FIELD_NUMBER: _ClassVar[int]
SUPPORTED_CERTIFICATE_KEY_TYPE_FIELD_NUMBER: _ClassVar[int]
VIDEO_RESOLUTION_CONSTRAINTS_FIELD_NUMBER: _ClassVar[int]
analog_output_capabilities: ClientIdentification.ClientCapabilities.AnalogOutputCapabilities
anti_rollback_usage_table: bool
can_disable_analog_output: bool
can_update_srm: bool
client_token: bool
max_hdcp_version: ClientIdentification.ClientCapabilities.HdcpVersion
oem_crypto_api_version: int
resource_rating_tier: int
session_token: bool
srm_version: int
supported_certificate_key_type: _containers.RepeatedScalarFieldContainer[ClientIdentification.ClientCapabilities.CertificateKeyType]
video_resolution_constraints: bool
def __init__(self, client_token: bool = ..., session_token: bool = ..., video_resolution_constraints: bool = ..., max_hdcp_version: _Optional[_Union[ClientIdentification.ClientCapabilities.HdcpVersion, str]] = ..., oem_crypto_api_version: _Optional[int] = ..., anti_rollback_usage_table: bool = ..., srm_version: _Optional[int] = ..., can_update_srm: bool = ..., supported_certificate_key_type: _Optional[_Iterable[_Union[ClientIdentification.ClientCapabilities.CertificateKeyType, str]]] = ..., analog_output_capabilities: _Optional[_Union[ClientIdentification.ClientCapabilities.AnalogOutputCapabilities, str]] = ..., can_disable_analog_output: bool = ..., resource_rating_tier: _Optional[int] = ...) -> None: ...
class ClientCredentials(_message.Message):
__slots__ = ["token", "type"]
TOKEN_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
token: bytes
type: ClientIdentification.TokenType
def __init__(self, type: _Optional[_Union[ClientIdentification.TokenType, str]] = ..., token: _Optional[bytes] = ...) -> None: ...
class NameValue(_message.Message):
__slots__ = ["name", "value"]
NAME_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
name: str
value: str
def __init__(self, name: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ...
CLIENT_CAPABILITIES_FIELD_NUMBER: _ClassVar[int]
CLIENT_INFO_FIELD_NUMBER: _ClassVar[int]
DEVICE_CREDENTIALS_FIELD_NUMBER: _ClassVar[int]
DRM_DEVICE_CERTIFICATE: ClientIdentification.TokenType
KEYBOX: ClientIdentification.TokenType
LICENSE_COUNTER_FIELD_NUMBER: _ClassVar[int]
OEM_DEVICE_CERTIFICATE: ClientIdentification.TokenType
PROVIDER_CLIENT_TOKEN_FIELD_NUMBER: _ClassVar[int]
REMOTE_ATTESTATION_CERTIFICATE: ClientIdentification.TokenType
TOKEN_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
VMP_DATA_FIELD_NUMBER: _ClassVar[int]
client_capabilities: ClientIdentification.ClientCapabilities
client_info: _containers.RepeatedCompositeFieldContainer[ClientIdentification.NameValue]
device_credentials: _containers.RepeatedCompositeFieldContainer[ClientIdentification.ClientCredentials]
license_counter: int
provider_client_token: bytes
token: bytes
type: ClientIdentification.TokenType
vmp_data: bytes
def __init__(self, type: _Optional[_Union[ClientIdentification.TokenType, str]] = ..., token: _Optional[bytes] = ..., client_info: _Optional[_Iterable[_Union[ClientIdentification.NameValue, _Mapping]]] = ..., provider_client_token: _Optional[bytes] = ..., license_counter: _Optional[int] = ..., client_capabilities: _Optional[_Union[ClientIdentification.ClientCapabilities, _Mapping]] = ..., vmp_data: _Optional[bytes] = ..., device_credentials: _Optional[_Iterable[_Union[ClientIdentification.ClientCredentials, _Mapping]]] = ...) -> None: ...
class DrmCertificate(_message.Message):
__slots__ = ["algorithm", "creation_time_seconds", "encryption_key", "expiration_time_seconds", "provider_id", "public_key", "rot_id", "serial_number", "service_types", "system_id", "test_device_deprecated", "type"]
class Algorithm(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class ServiceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class EncryptionKey(_message.Message):
__slots__ = ["algorithm", "public_key"]
ALGORITHM_FIELD_NUMBER: _ClassVar[int]
PUBLIC_KEY_FIELD_NUMBER: _ClassVar[int]
algorithm: DrmCertificate.Algorithm
public_key: bytes
def __init__(self, public_key: _Optional[bytes] = ..., algorithm: _Optional[_Union[DrmCertificate.Algorithm, str]] = ...) -> None: ...
ALGORITHM_FIELD_NUMBER: _ClassVar[int]
CAS_PROXY_SDK: DrmCertificate.ServiceType
CREATION_TIME_SECONDS_FIELD_NUMBER: _ClassVar[int]
DEVICE: DrmCertificate.Type
DEVICE_MODEL: DrmCertificate.Type
ECC_SECP256R1: DrmCertificate.Algorithm
ECC_SECP384R1: DrmCertificate.Algorithm
ECC_SECP521R1: DrmCertificate.Algorithm
ENCRYPTION_KEY_FIELD_NUMBER: _ClassVar[int]
EXPIRATION_TIME_SECONDS_FIELD_NUMBER: _ClassVar[int]
LICENSE_SERVER_PROXY_SDK: DrmCertificate.ServiceType
LICENSE_SERVER_SDK: DrmCertificate.ServiceType
PROVIDER_ID_FIELD_NUMBER: _ClassVar[int]
PROVISIONER: DrmCertificate.Type
PROVISIONING_SDK: DrmCertificate.ServiceType
PUBLIC_KEY_FIELD_NUMBER: _ClassVar[int]
ROOT: DrmCertificate.Type
ROT_ID_FIELD_NUMBER: _ClassVar[int]
RSA: DrmCertificate.Algorithm
SERIAL_NUMBER_FIELD_NUMBER: _ClassVar[int]
SERVICE: DrmCertificate.Type
SERVICE_TYPES_FIELD_NUMBER: _ClassVar[int]
SYSTEM_ID_FIELD_NUMBER: _ClassVar[int]
TEST_DEVICE_DEPRECATED_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
UNKNOWN_ALGORITHM: DrmCertificate.Algorithm
UNKNOWN_SERVICE_TYPE: DrmCertificate.ServiceType
algorithm: DrmCertificate.Algorithm
creation_time_seconds: int
encryption_key: DrmCertificate.EncryptionKey
expiration_time_seconds: int
provider_id: str
public_key: bytes
rot_id: bytes
serial_number: bytes
service_types: _containers.RepeatedScalarFieldContainer[DrmCertificate.ServiceType]
system_id: int
test_device_deprecated: bool
type: DrmCertificate.Type
def __init__(self, type: _Optional[_Union[DrmCertificate.Type, str]] = ..., serial_number: _Optional[bytes] = ..., creation_time_seconds: _Optional[int] = ..., expiration_time_seconds: _Optional[int] = ..., public_key: _Optional[bytes] = ..., system_id: _Optional[int] = ..., test_device_deprecated: bool = ..., provider_id: _Optional[str] = ..., service_types: _Optional[_Iterable[_Union[DrmCertificate.ServiceType, str]]] = ..., algorithm: _Optional[_Union[DrmCertificate.Algorithm, str]] = ..., rot_id: _Optional[bytes] = ..., encryption_key: _Optional[_Union[DrmCertificate.EncryptionKey, _Mapping]] = ...) -> None: ...
class EncryptedClientIdentification(_message.Message):
__slots__ = ["encrypted_client_id", "encrypted_client_id_iv", "encrypted_privacy_key", "provider_id", "service_certificate_serial_number"]
ENCRYPTED_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
ENCRYPTED_CLIENT_ID_IV_FIELD_NUMBER: _ClassVar[int]
ENCRYPTED_PRIVACY_KEY_FIELD_NUMBER: _ClassVar[int]
PROVIDER_ID_FIELD_NUMBER: _ClassVar[int]
SERVICE_CERTIFICATE_SERIAL_NUMBER_FIELD_NUMBER: _ClassVar[int]
encrypted_client_id: bytes
encrypted_client_id_iv: bytes
encrypted_privacy_key: bytes
provider_id: str
service_certificate_serial_number: bytes
def __init__(self, provider_id: _Optional[str] = ..., service_certificate_serial_number: _Optional[bytes] = ..., encrypted_client_id: _Optional[bytes] = ..., encrypted_client_id_iv: _Optional[bytes] = ..., encrypted_privacy_key: _Optional[bytes] = ...) -> None: ...
class FileHashes(_message.Message):
__slots__ = ["signatures", "signer"]
class Signature(_message.Message):
__slots__ = ["SHA512Hash", "filename", "main_exe", "signature", "test_signing"]
FILENAME_FIELD_NUMBER: _ClassVar[int]
MAIN_EXE_FIELD_NUMBER: _ClassVar[int]
SHA512HASH_FIELD_NUMBER: _ClassVar[int]
SHA512Hash: bytes
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
TEST_SIGNING_FIELD_NUMBER: _ClassVar[int]
filename: str
main_exe: bool
signature: bytes
test_signing: bool
def __init__(self, filename: _Optional[str] = ..., test_signing: bool = ..., SHA512Hash: _Optional[bytes] = ..., main_exe: bool = ..., signature: _Optional[bytes] = ...) -> None: ...
SIGNATURES_FIELD_NUMBER: _ClassVar[int]
SIGNER_FIELD_NUMBER: _ClassVar[int]
signatures: _containers.RepeatedCompositeFieldContainer[FileHashes.Signature]
signer: bytes
def __init__(self, signer: _Optional[bytes] = ..., signatures: _Optional[_Iterable[_Union[FileHashes.Signature, _Mapping]]] = ...) -> None: ...
class License(_message.Message):
__slots__ = ["group_ids", "id", "key", "license_start_time", "platform_verification_status", "policy", "protection_scheme", "provider_client_token", "remote_attestation_verified", "srm_requirement", "srm_update"]
class KeyContainer(_message.Message):
__slots__ = ["anti_rollback_usage_table", "id", "iv", "key", "key_control", "level", "operator_session_key_permissions", "requested_protection", "required_protection", "track_label", "type", "video_resolution_constraints"]
class KeyType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class SecurityLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class KeyControl(_message.Message):
__slots__ = ["iv", "key_control_block"]
IV_FIELD_NUMBER: _ClassVar[int]
KEY_CONTROL_BLOCK_FIELD_NUMBER: _ClassVar[int]
iv: bytes
key_control_block: bytes
def __init__(self, key_control_block: _Optional[bytes] = ..., iv: _Optional[bytes] = ...) -> None: ...
class OperatorSessionKeyPermissions(_message.Message):
__slots__ = ["allow_decrypt", "allow_encrypt", "allow_sign", "allow_signature_verify"]
ALLOW_DECRYPT_FIELD_NUMBER: _ClassVar[int]
ALLOW_ENCRYPT_FIELD_NUMBER: _ClassVar[int]
ALLOW_SIGNATURE_VERIFY_FIELD_NUMBER: _ClassVar[int]
ALLOW_SIGN_FIELD_NUMBER: _ClassVar[int]
allow_decrypt: bool
allow_encrypt: bool
allow_sign: bool
allow_signature_verify: bool
def __init__(self, allow_encrypt: bool = ..., allow_decrypt: bool = ..., allow_sign: bool = ..., allow_signature_verify: bool = ...) -> None: ...
class OutputProtection(_message.Message):
__slots__ = ["cgms_flags", "disable_analog_output", "disable_digital_output", "hdcp", "hdcp_srm_rule"]
class CGMS(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class HDCP(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class HdcpSrmRule(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
CGMS_FLAGS_FIELD_NUMBER: _ClassVar[int]
CGMS_NONE: License.KeyContainer.OutputProtection.CGMS
COPY_FREE: License.KeyContainer.OutputProtection.CGMS
COPY_NEVER: License.KeyContainer.OutputProtection.CGMS
COPY_ONCE: License.KeyContainer.OutputProtection.CGMS
CURRENT_SRM: License.KeyContainer.OutputProtection.HdcpSrmRule
DISABLE_ANALOG_OUTPUT_FIELD_NUMBER: _ClassVar[int]
DISABLE_DIGITAL_OUTPUT_FIELD_NUMBER: _ClassVar[int]
HDCP_FIELD_NUMBER: _ClassVar[int]
HDCP_NONE: License.KeyContainer.OutputProtection.HDCP
HDCP_NO_DIGITAL_OUTPUT: License.KeyContainer.OutputProtection.HDCP
HDCP_SRM_RULE_FIELD_NUMBER: _ClassVar[int]
HDCP_SRM_RULE_NONE: License.KeyContainer.OutputProtection.HdcpSrmRule
HDCP_V1: License.KeyContainer.OutputProtection.HDCP
HDCP_V2: License.KeyContainer.OutputProtection.HDCP
HDCP_V2_1: License.KeyContainer.OutputProtection.HDCP
HDCP_V2_2: License.KeyContainer.OutputProtection.HDCP
HDCP_V2_3: License.KeyContainer.OutputProtection.HDCP
cgms_flags: License.KeyContainer.OutputProtection.CGMS
disable_analog_output: bool
disable_digital_output: bool
hdcp: License.KeyContainer.OutputProtection.HDCP
hdcp_srm_rule: License.KeyContainer.OutputProtection.HdcpSrmRule
def __init__(self, hdcp: _Optional[_Union[License.KeyContainer.OutputProtection.HDCP, str]] = ..., cgms_flags: _Optional[_Union[License.KeyContainer.OutputProtection.CGMS, str]] = ..., hdcp_srm_rule: _Optional[_Union[License.KeyContainer.OutputProtection.HdcpSrmRule, str]] = ..., disable_analog_output: bool = ..., disable_digital_output: bool = ...) -> None: ...
class VideoResolutionConstraint(_message.Message):
__slots__ = ["max_resolution_pixels", "min_resolution_pixels", "required_protection"]
MAX_RESOLUTION_PIXELS_FIELD_NUMBER: _ClassVar[int]
MIN_RESOLUTION_PIXELS_FIELD_NUMBER: _ClassVar[int]
REQUIRED_PROTECTION_FIELD_NUMBER: _ClassVar[int]
max_resolution_pixels: int
min_resolution_pixels: int
required_protection: License.KeyContainer.OutputProtection
def __init__(self, min_resolution_pixels: _Optional[int] = ..., max_resolution_pixels: _Optional[int] = ..., required_protection: _Optional[_Union[License.KeyContainer.OutputProtection, _Mapping]] = ...) -> None: ...
ANTI_ROLLBACK_USAGE_TABLE_FIELD_NUMBER: _ClassVar[int]
CONTENT: License.KeyContainer.KeyType
ENTITLEMENT: License.KeyContainer.KeyType
HW_SECURE_ALL: License.KeyContainer.SecurityLevel
HW_SECURE_CRYPTO: License.KeyContainer.SecurityLevel
HW_SECURE_DECODE: License.KeyContainer.SecurityLevel
ID_FIELD_NUMBER: _ClassVar[int]
IV_FIELD_NUMBER: _ClassVar[int]
KEY_CONTROL: License.KeyContainer.KeyType
KEY_CONTROL_FIELD_NUMBER: _ClassVar[int]
KEY_FIELD_NUMBER: _ClassVar[int]
LEVEL_FIELD_NUMBER: _ClassVar[int]
OEM_CONTENT: License.KeyContainer.KeyType
OPERATOR_SESSION: License.KeyContainer.KeyType
OPERATOR_SESSION_KEY_PERMISSIONS_FIELD_NUMBER: _ClassVar[int]
REQUESTED_PROTECTION_FIELD_NUMBER: _ClassVar[int]
REQUIRED_PROTECTION_FIELD_NUMBER: _ClassVar[int]
SIGNING: License.KeyContainer.KeyType
SW_SECURE_CRYPTO: License.KeyContainer.SecurityLevel
SW_SECURE_DECODE: License.KeyContainer.SecurityLevel
TRACK_LABEL_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
VIDEO_RESOLUTION_CONSTRAINTS_FIELD_NUMBER: _ClassVar[int]
anti_rollback_usage_table: bool
id: bytes
iv: bytes
key: bytes
key_control: License.KeyContainer.KeyControl
level: License.KeyContainer.SecurityLevel
operator_session_key_permissions: License.KeyContainer.OperatorSessionKeyPermissions
requested_protection: License.KeyContainer.OutputProtection
required_protection: License.KeyContainer.OutputProtection
track_label: str
type: License.KeyContainer.KeyType
video_resolution_constraints: _containers.RepeatedCompositeFieldContainer[License.KeyContainer.VideoResolutionConstraint]
def __init__(self, id: _Optional[bytes] = ..., iv: _Optional[bytes] = ..., key: _Optional[bytes] = ..., type: _Optional[_Union[License.KeyContainer.KeyType, str]] = ..., level: _Optional[_Union[License.KeyContainer.SecurityLevel, str]] = ..., required_protection: _Optional[_Union[License.KeyContainer.OutputProtection, _Mapping]] = ..., requested_protection: _Optional[_Union[License.KeyContainer.OutputProtection, _Mapping]] = ..., key_control: _Optional[_Union[License.KeyContainer.KeyControl, _Mapping]] = ..., operator_session_key_permissions: _Optional[_Union[License.KeyContainer.OperatorSessionKeyPermissions, _Mapping]] = ..., video_resolution_constraints: _Optional[_Iterable[_Union[License.KeyContainer.VideoResolutionConstraint, _Mapping]]] = ..., anti_rollback_usage_table: bool = ..., track_label: _Optional[str] = ...) -> None: ...
class Policy(_message.Message):
__slots__ = ["always_include_client_id", "can_persist", "can_play", "can_renew", "license_duration_seconds", "play_start_grace_period_seconds", "playback_duration_seconds", "renew_with_usage", "renewal_delay_seconds", "renewal_recovery_duration_seconds", "renewal_retry_interval_seconds", "renewal_server_url", "rental_duration_seconds", "soft_enforce_playback_duration", "soft_enforce_rental_duration"]
ALWAYS_INCLUDE_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
CAN_PERSIST_FIELD_NUMBER: _ClassVar[int]
CAN_PLAY_FIELD_NUMBER: _ClassVar[int]
CAN_RENEW_FIELD_NUMBER: _ClassVar[int]
LICENSE_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int]
PLAYBACK_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int]
PLAY_START_GRACE_PERIOD_SECONDS_FIELD_NUMBER: _ClassVar[int]
RENEWAL_DELAY_SECONDS_FIELD_NUMBER: _ClassVar[int]
RENEWAL_RECOVERY_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int]
RENEWAL_RETRY_INTERVAL_SECONDS_FIELD_NUMBER: _ClassVar[int]
RENEWAL_SERVER_URL_FIELD_NUMBER: _ClassVar[int]
RENEW_WITH_USAGE_FIELD_NUMBER: _ClassVar[int]
RENTAL_DURATION_SECONDS_FIELD_NUMBER: _ClassVar[int]
SOFT_ENFORCE_PLAYBACK_DURATION_FIELD_NUMBER: _ClassVar[int]
SOFT_ENFORCE_RENTAL_DURATION_FIELD_NUMBER: _ClassVar[int]
always_include_client_id: bool
can_persist: bool
can_play: bool
can_renew: bool
license_duration_seconds: int
play_start_grace_period_seconds: int
playback_duration_seconds: int
renew_with_usage: bool
renewal_delay_seconds: int
renewal_recovery_duration_seconds: int
renewal_retry_interval_seconds: int
renewal_server_url: str
rental_duration_seconds: int
soft_enforce_playback_duration: bool
soft_enforce_rental_duration: bool
def __init__(self, can_play: bool = ..., can_persist: bool = ..., can_renew: bool = ..., rental_duration_seconds: _Optional[int] = ..., playback_duration_seconds: _Optional[int] = ..., license_duration_seconds: _Optional[int] = ..., renewal_recovery_duration_seconds: _Optional[int] = ..., renewal_server_url: _Optional[str] = ..., renewal_delay_seconds: _Optional[int] = ..., renewal_retry_interval_seconds: _Optional[int] = ..., renew_with_usage: bool = ..., always_include_client_id: bool = ..., play_start_grace_period_seconds: _Optional[int] = ..., soft_enforce_playback_duration: bool = ..., soft_enforce_rental_duration: bool = ...) -> None: ...
GROUP_IDS_FIELD_NUMBER: _ClassVar[int]
ID_FIELD_NUMBER: _ClassVar[int]
KEY_FIELD_NUMBER: _ClassVar[int]
LICENSE_START_TIME_FIELD_NUMBER: _ClassVar[int]
PLATFORM_VERIFICATION_STATUS_FIELD_NUMBER: _ClassVar[int]
POLICY_FIELD_NUMBER: _ClassVar[int]
PROTECTION_SCHEME_FIELD_NUMBER: _ClassVar[int]
PROVIDER_CLIENT_TOKEN_FIELD_NUMBER: _ClassVar[int]
REMOTE_ATTESTATION_VERIFIED_FIELD_NUMBER: _ClassVar[int]
SRM_REQUIREMENT_FIELD_NUMBER: _ClassVar[int]
SRM_UPDATE_FIELD_NUMBER: _ClassVar[int]
group_ids: _containers.RepeatedScalarFieldContainer[bytes]
id: LicenseIdentification
key: _containers.RepeatedCompositeFieldContainer[License.KeyContainer]
license_start_time: int
platform_verification_status: PlatformVerificationStatus
policy: License.Policy
protection_scheme: int
provider_client_token: bytes
remote_attestation_verified: bool
srm_requirement: bytes
srm_update: bytes
def __init__(self, id: _Optional[_Union[LicenseIdentification, _Mapping]] = ..., policy: _Optional[_Union[License.Policy, _Mapping]] = ..., key: _Optional[_Iterable[_Union[License.KeyContainer, _Mapping]]] = ..., license_start_time: _Optional[int] = ..., remote_attestation_verified: bool = ..., provider_client_token: _Optional[bytes] = ..., protection_scheme: _Optional[int] = ..., srm_requirement: _Optional[bytes] = ..., srm_update: _Optional[bytes] = ..., platform_verification_status: _Optional[_Union[PlatformVerificationStatus, str]] = ..., group_ids: _Optional[_Iterable[bytes]] = ...) -> None: ...
class LicenseIdentification(_message.Message):
__slots__ = ["provider_session_token", "purchase_id", "request_id", "session_id", "type", "version"]
PROVIDER_SESSION_TOKEN_FIELD_NUMBER: _ClassVar[int]
PURCHASE_ID_FIELD_NUMBER: _ClassVar[int]
REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
VERSION_FIELD_NUMBER: _ClassVar[int]
provider_session_token: bytes
purchase_id: bytes
request_id: bytes
session_id: bytes
type: LicenseType
version: int
def __init__(self, request_id: _Optional[bytes] = ..., session_id: _Optional[bytes] = ..., purchase_id: _Optional[bytes] = ..., type: _Optional[_Union[LicenseType, str]] = ..., version: _Optional[int] = ..., provider_session_token: _Optional[bytes] = ...) -> None: ...
class LicenseRequest(_message.Message):
__slots__ = ["client_id", "content_id", "encrypted_client_id", "key_control_nonce", "key_control_nonce_deprecated", "protocol_version", "request_time", "type"]
class RequestType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class ContentIdentification(_message.Message):
__slots__ = ["existing_license", "init_data", "webm_key_id", "widevine_pssh_data"]
class ExistingLicense(_message.Message):
__slots__ = ["license_id", "seconds_since_last_played", "seconds_since_started", "session_usage_table_entry"]
LICENSE_ID_FIELD_NUMBER: _ClassVar[int]
SECONDS_SINCE_LAST_PLAYED_FIELD_NUMBER: _ClassVar[int]
SECONDS_SINCE_STARTED_FIELD_NUMBER: _ClassVar[int]
SESSION_USAGE_TABLE_ENTRY_FIELD_NUMBER: _ClassVar[int]
license_id: LicenseIdentification
seconds_since_last_played: int
seconds_since_started: int
session_usage_table_entry: bytes
def __init__(self, license_id: _Optional[_Union[LicenseIdentification, _Mapping]] = ..., seconds_since_started: _Optional[int] = ..., seconds_since_last_played: _Optional[int] = ..., session_usage_table_entry: _Optional[bytes] = ...) -> None: ...
class InitData(_message.Message):
__slots__ = ["init_data", "init_data_type", "license_type", "request_id"]
class InitDataType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
CENC: LicenseRequest.ContentIdentification.InitData.InitDataType
INIT_DATA_FIELD_NUMBER: _ClassVar[int]
INIT_DATA_TYPE_FIELD_NUMBER: _ClassVar[int]
LICENSE_TYPE_FIELD_NUMBER: _ClassVar[int]
REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
WEBM: LicenseRequest.ContentIdentification.InitData.InitDataType
init_data: bytes
init_data_type: LicenseRequest.ContentIdentification.InitData.InitDataType
license_type: LicenseType
request_id: bytes
def __init__(self, init_data_type: _Optional[_Union[LicenseRequest.ContentIdentification.InitData.InitDataType, str]] = ..., init_data: _Optional[bytes] = ..., license_type: _Optional[_Union[LicenseType, str]] = ..., request_id: _Optional[bytes] = ...) -> None: ...
class WebmKeyId(_message.Message):
__slots__ = ["header", "license_type", "request_id"]
HEADER_FIELD_NUMBER: _ClassVar[int]
LICENSE_TYPE_FIELD_NUMBER: _ClassVar[int]
REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
header: bytes
license_type: LicenseType
request_id: bytes
def __init__(self, header: _Optional[bytes] = ..., license_type: _Optional[_Union[LicenseType, str]] = ..., request_id: _Optional[bytes] = ...) -> None: ...
class WidevinePsshData(_message.Message):
__slots__ = ["license_type", "pssh_data", "request_id"]
LICENSE_TYPE_FIELD_NUMBER: _ClassVar[int]
PSSH_DATA_FIELD_NUMBER: _ClassVar[int]
REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
license_type: LicenseType
pssh_data: _containers.RepeatedScalarFieldContainer[bytes]
request_id: bytes
def __init__(self, pssh_data: _Optional[_Iterable[bytes]] = ..., license_type: _Optional[_Union[LicenseType, str]] = ..., request_id: _Optional[bytes] = ...) -> None: ...
EXISTING_LICENSE_FIELD_NUMBER: _ClassVar[int]
INIT_DATA_FIELD_NUMBER: _ClassVar[int]
WEBM_KEY_ID_FIELD_NUMBER: _ClassVar[int]
WIDEVINE_PSSH_DATA_FIELD_NUMBER: _ClassVar[int]
existing_license: LicenseRequest.ContentIdentification.ExistingLicense
init_data: LicenseRequest.ContentIdentification.InitData
webm_key_id: LicenseRequest.ContentIdentification.WebmKeyId
widevine_pssh_data: LicenseRequest.ContentIdentification.WidevinePsshData
def __init__(self, widevine_pssh_data: _Optional[_Union[LicenseRequest.ContentIdentification.WidevinePsshData, _Mapping]] = ..., webm_key_id: _Optional[_Union[LicenseRequest.ContentIdentification.WebmKeyId, _Mapping]] = ..., existing_license: _Optional[_Union[LicenseRequest.ContentIdentification.ExistingLicense, _Mapping]] = ..., init_data: _Optional[_Union[LicenseRequest.ContentIdentification.InitData, _Mapping]] = ...) -> None: ...
CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
CONTENT_ID_FIELD_NUMBER: _ClassVar[int]
ENCRYPTED_CLIENT_ID_FIELD_NUMBER: _ClassVar[int]
KEY_CONTROL_NONCE_DEPRECATED_FIELD_NUMBER: _ClassVar[int]
KEY_CONTROL_NONCE_FIELD_NUMBER: _ClassVar[int]
NEW: LicenseRequest.RequestType
PROTOCOL_VERSION_FIELD_NUMBER: _ClassVar[int]
RELEASE: LicenseRequest.RequestType
RENEWAL: LicenseRequest.RequestType
REQUEST_TIME_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
client_id: ClientIdentification
content_id: LicenseRequest.ContentIdentification
encrypted_client_id: EncryptedClientIdentification
key_control_nonce: int
key_control_nonce_deprecated: bytes
protocol_version: ProtocolVersion
request_time: int
type: LicenseRequest.RequestType
def __init__(self, client_id: _Optional[_Union[ClientIdentification, _Mapping]] = ..., content_id: _Optional[_Union[LicenseRequest.ContentIdentification, _Mapping]] = ..., type: _Optional[_Union[LicenseRequest.RequestType, str]] = ..., request_time: _Optional[int] = ..., key_control_nonce_deprecated: _Optional[bytes] = ..., protocol_version: _Optional[_Union[ProtocolVersion, str]] = ..., key_control_nonce: _Optional[int] = ..., encrypted_client_id: _Optional[_Union[EncryptedClientIdentification, _Mapping]] = ...) -> None: ...
class MetricData(_message.Message):
__slots__ = ["metric_data", "stage_name"]
class MetricType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class TypeValue(_message.Message):
__slots__ = ["type", "value"]
TYPE_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
type: MetricData.MetricType
value: int
def __init__(self, type: _Optional[_Union[MetricData.MetricType, str]] = ..., value: _Optional[int] = ...) -> None: ...
LATENCY: MetricData.MetricType
METRIC_DATA_FIELD_NUMBER: _ClassVar[int]
STAGE_NAME_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP: MetricData.MetricType
metric_data: _containers.RepeatedCompositeFieldContainer[MetricData.TypeValue]
stage_name: str
def __init__(self, stage_name: _Optional[str] = ..., metric_data: _Optional[_Iterable[_Union[MetricData.TypeValue, _Mapping]]] = ...) -> None: ...
class SignedDrmCertificate(_message.Message):
__slots__ = ["drm_certificate", "hash_algorithm", "signature", "signer"]
DRM_CERTIFICATE_FIELD_NUMBER: _ClassVar[int]
HASH_ALGORITHM_FIELD_NUMBER: _ClassVar[int]
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
SIGNER_FIELD_NUMBER: _ClassVar[int]
drm_certificate: bytes
hash_algorithm: HashAlgorithmProto
signature: bytes
signer: SignedDrmCertificate
def __init__(self, drm_certificate: _Optional[bytes] = ..., signature: _Optional[bytes] = ..., signer: _Optional[_Union[SignedDrmCertificate, _Mapping]] = ..., hash_algorithm: _Optional[_Union[HashAlgorithmProto, str]] = ...) -> None: ...
class SignedMessage(_message.Message):
__slots__ = ["metric_data", "msg", "oemcrypto_core_message", "remote_attestation", "service_version_info", "session_key", "session_key_type", "signature", "type"]
class MessageType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class SessionKeyType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
CAS_LICENSE: SignedMessage.MessageType
CAS_LICENSE_REQUEST: SignedMessage.MessageType
EPHERMERAL_ECC_PUBLIC_KEY: SignedMessage.SessionKeyType
ERROR_RESPONSE: SignedMessage.MessageType
EXTERNAL_LICENSE: SignedMessage.MessageType
EXTERNAL_LICENSE_REQUEST: SignedMessage.MessageType
LICENSE: SignedMessage.MessageType
LICENSE_REQUEST: SignedMessage.MessageType
METRIC_DATA_FIELD_NUMBER: _ClassVar[int]
MSG_FIELD_NUMBER: _ClassVar[int]
OEMCRYPTO_CORE_MESSAGE_FIELD_NUMBER: _ClassVar[int]
REMOTE_ATTESTATION_FIELD_NUMBER: _ClassVar[int]
SERVICE_CERTIFICATE: SignedMessage.MessageType
SERVICE_CERTIFICATE_REQUEST: SignedMessage.MessageType
SERVICE_VERSION_INFO_FIELD_NUMBER: _ClassVar[int]
SESSION_KEY_FIELD_NUMBER: _ClassVar[int]
SESSION_KEY_TYPE_FIELD_NUMBER: _ClassVar[int]
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
SUB_LICENSE: SignedMessage.MessageType
TYPE_FIELD_NUMBER: _ClassVar[int]
UNDEFINED: SignedMessage.SessionKeyType
WRAPPED_AES_KEY: SignedMessage.SessionKeyType
metric_data: _containers.RepeatedCompositeFieldContainer[MetricData]
msg: bytes
oemcrypto_core_message: bytes
remote_attestation: bytes
service_version_info: VersionInfo
session_key: bytes
session_key_type: SignedMessage.SessionKeyType
signature: bytes
type: SignedMessage.MessageType
def __init__(self, type: _Optional[_Union[SignedMessage.MessageType, str]] = ..., msg: _Optional[bytes] = ..., signature: _Optional[bytes] = ..., session_key: _Optional[bytes] = ..., remote_attestation: _Optional[bytes] = ..., metric_data: _Optional[_Iterable[_Union[MetricData, _Mapping]]] = ..., service_version_info: _Optional[_Union[VersionInfo, _Mapping]] = ..., session_key_type: _Optional[_Union[SignedMessage.SessionKeyType, str]] = ..., oemcrypto_core_message: _Optional[bytes] = ...) -> None: ...
class VersionInfo(_message.Message):
__slots__ = ["license_sdk_version", "license_service_version"]
LICENSE_SDK_VERSION_FIELD_NUMBER: _ClassVar[int]
LICENSE_SERVICE_VERSION_FIELD_NUMBER: _ClassVar[int]
license_sdk_version: str
license_service_version: str
def __init__(self, license_sdk_version: _Optional[str] = ..., license_service_version: _Optional[str] = ...) -> None: ...
class WidevinePsshData(_message.Message):
__slots__ = ["algorithm", "content_id", "crypto_period_index", "crypto_period_seconds", "entitled_keys", "group_ids", "grouped_license", "key_ids", "key_sequence", "policy", "protection_scheme", "provider", "track_type", "type", "video_feature"]
class Algorithm(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class Type(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class EntitledKey(_message.Message):
__slots__ = ["entitlement_key_id", "entitlement_key_size_bytes", "iv", "key", "key_id"]
ENTITLEMENT_KEY_ID_FIELD_NUMBER: _ClassVar[int]
ENTITLEMENT_KEY_SIZE_BYTES_FIELD_NUMBER: _ClassVar[int]
IV_FIELD_NUMBER: _ClassVar[int]
KEY_FIELD_NUMBER: _ClassVar[int]
KEY_ID_FIELD_NUMBER: _ClassVar[int]
entitlement_key_id: bytes
entitlement_key_size_bytes: int
iv: bytes
key: bytes
key_id: bytes
def __init__(self, entitlement_key_id: _Optional[bytes] = ..., key_id: _Optional[bytes] = ..., key: _Optional[bytes] = ..., iv: _Optional[bytes] = ..., entitlement_key_size_bytes: _Optional[int] = ...) -> None: ...
AESCTR: WidevinePsshData.Algorithm
ALGORITHM_FIELD_NUMBER: _ClassVar[int]
CONTENT_ID_FIELD_NUMBER: _ClassVar[int]
CRYPTO_PERIOD_INDEX_FIELD_NUMBER: _ClassVar[int]
CRYPTO_PERIOD_SECONDS_FIELD_NUMBER: _ClassVar[int]
ENTITLED_KEY: WidevinePsshData.Type
ENTITLED_KEYS_FIELD_NUMBER: _ClassVar[int]
ENTITLEMENT: WidevinePsshData.Type
GROUPED_LICENSE_FIELD_NUMBER: _ClassVar[int]
GROUP_IDS_FIELD_NUMBER: _ClassVar[int]
KEY_IDS_FIELD_NUMBER: _ClassVar[int]
KEY_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
POLICY_FIELD_NUMBER: _ClassVar[int]
PROTECTION_SCHEME_FIELD_NUMBER: _ClassVar[int]
PROVIDER_FIELD_NUMBER: _ClassVar[int]
SINGLE: WidevinePsshData.Type
TRACK_TYPE_FIELD_NUMBER: _ClassVar[int]
TYPE_FIELD_NUMBER: _ClassVar[int]
UNENCRYPTED: WidevinePsshData.Algorithm
VIDEO_FEATURE_FIELD_NUMBER: _ClassVar[int]
algorithm: WidevinePsshData.Algorithm
content_id: bytes
crypto_period_index: int
crypto_period_seconds: int
entitled_keys: _containers.RepeatedCompositeFieldContainer[WidevinePsshData.EntitledKey]
group_ids: _containers.RepeatedScalarFieldContainer[bytes]
grouped_license: bytes
key_ids: _containers.RepeatedScalarFieldContainer[bytes]
key_sequence: int
policy: str
protection_scheme: int
provider: str
track_type: str
type: WidevinePsshData.Type
video_feature: str
def __init__(self, key_ids: _Optional[_Iterable[bytes]] = ..., content_id: _Optional[bytes] = ..., crypto_period_index: _Optional[int] = ..., protection_scheme: _Optional[int] = ..., crypto_period_seconds: _Optional[int] = ..., type: _Optional[_Union[WidevinePsshData.Type, str]] = ..., key_sequence: _Optional[int] = ..., group_ids: _Optional[_Iterable[bytes]] = ..., entitled_keys: _Optional[_Iterable[_Union[WidevinePsshData.EntitledKey, _Mapping]]] = ..., video_feature: _Optional[str] = ..., algorithm: _Optional[_Union[WidevinePsshData.Algorithm, str]] = ..., provider: _Optional[str] = ..., track_type: _Optional[str] = ..., policy: _Optional[str] = ..., grouped_license: _Optional[bytes] = ...) -> None: ...
class LicenseType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class PlatformVerificationStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class ProtocolVersion(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
class HashAlgorithmProto(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []

206
extractor/script.js Normal file
View File

@ -0,0 +1,206 @@
/**
* KeyDive: Widevine L3 Extractor for Android Devices
* Enhances DRM key extraction for research and educational purposes.
* Source: https://github.com/hyugogirubato/KeyDive
*/
const SDK_API = '${SDK_API}'; // Dynamically replaced with the actual SDK API level.
const OEM_CRYPTO_API = [
// Mapping of function names across different API levels (obfuscated names may vary).
'rnmsglvj',
'polorucp',
'kqzqahjq',
'pldrclfq',
'kgaitijd',
'cwkfcplc',
'crhqcdet',
'ulns', // 11, 13
'dnvffnze', // 15
'ygjiljer', // 15
'qbjxtubz', // 16
'qkfrcjtw', // 16
'rbhjspoh' // 17
// Add more as needed for different versions.
];
// Logging levels to synchronize with Python's logging module.
const Level = {
NOTSET: 0,
DEBUG: 10,
INFO: 20,
// WARN: WARNING,
WARNING: 30,
ERROR: 40,
// FATAL: CRITICAL,
CRITICAL: 50
};
// Utility for encoding strings into byte arrays.
// https://gist.github.com/Yaffle/5458286#file-textencodertextdecoder-js
function TextEncoder() {}
TextEncoder.prototype.encode = function (string) {
let octets = [];
let i = 0;
while (i < string.length) {
let codePoint = string.codePointAt(i);
let c = 0;
let bits = 0;
if (codePoint <= 0x007F) {
c = 0;
bits = 0x00;
} else if (codePoint <= 0x07FF) {
c = 6;
bits = 0xC0;
} else if (codePoint <= 0xFFFF) {
c = 12;
bits = 0xE0;
} else if (codePoint <= 0x1FFFFF) {
c = 18;
bits = 0xF0;
}
octets.push(bits | (codePoint >> c));
while (c >= 6) {
c -= 6;
octets.push(0x80 | ((codePoint >> c) & 0x3F));
}
i += codePoint >= 0x10000 ? 2 : 1;
}
return octets;
};
const print = (level, message) => {
message = typeof message === 'object' ? JSON.stringify(message) : message;
send(level, new TextEncoder().encode(message));
}
// Identifies and returns the specified library.
const getLibrary = (name) => Process.getModuleByName(name);
// Hooks into specified functions within a library, aiming to extract keys and disable privacy mode.
const hookLibrary = (name) => {
// https://github.com/poxyran/misc/blob/master/frida-enumerate-imports.py
const library = getLibrary(name);
const functions = [...library.enumerateExports(), ...library.enumerateImports()];
const targetFunction = functions.find(func => OEM_CRYPTO_API.includes(func.name));
let hookedCount = 0;
functions.forEach((func) => {
const funcName = func.name;
const funcAddr = func.address;
try {
let funcHooked = true;
if (funcName.includes('UsePrivacyMode')) {
disablePrivacyMode(funcAddr);
} else if (funcName.includes('PrepareKeyRequest')) {
prepareKeyRequest(funcAddr);
} else if (targetFunction === func || (!targetFunction && funcName.match(/^[a-z]+$/))) {
getPrivateKey(funcAddr);
} else {
funcHooked = false;
}
if (funcHooked) {
hookedCount++;
print(Level.DEBUG, `Hooked (${funcAddr}): ${funcName}`);
}
} catch (e) {
print(Level.ERROR, `${funcName} (${funcAddr}): ${e.message}`);
}
});
if (hookedCount < 3) {
print(Level.ERROR, 'Insufficient functions hooked');
return false;
}
return true;
}
const disablePrivacyMode = (address) => {
Interceptor.attach(ptr(address), {
onLeave: function (retval) {
retval.replace(ptr(0));
}
});
}
const prepareKeyRequest = (address) => {
Interceptor.attach(ptr(address), {
onEnter: function (args) {
let index;
if ([23, 31, 32, 33].includes(SDK_API)) {
index = 5;
} else if ([24, 25, 26, 27, 28, 29, 30].includes(SDK_API)) {
index = 4;
} else {
index = 5; // Default index assignment
print(Level.WARNING, SDK_API < 23 ? 'SDK API too old' : 'SDK API not implemented');
print(Level.WARNING, `Defaulting to args[${index}] for PrepareKeyRequest`);
}
this.ret = args[index];
},
onLeave: function () {
if (this.ret) {
const size = Memory.readU32(ptr(this.ret).add(Process.pointerSize));
const data = Memory.readByteArray(this.ret.add(Process.pointerSize * 2).readPointer(), size);
send('device_info', data);
}
}
});
}
const getPrivateKey = (address) => {
Interceptor.attach(ptr(address), {
onEnter: function (args) {
if (!args[6].isNull()) {
const size = args[6].toInt32();
if (size >= 1000 && size <= 2000 && !args[5].isNull()) {
const buffer = args[5].readByteArray(size);
const bytes = new Uint8Array(buffer);
// Check for DER encoding markers for the beginning of a private key (MII).
if (bytes[0] === 0x30 && bytes[1] === 0x82) {
try {
// Attempt to extract and send the private key.
const binaryString = a2bs(bytes);
const keyLength = getKeyLength(binaryString); // ASN.1 DER
const key = bytes.slice(0, keyLength);
print(Level.DEBUG, `Function getPrivateKey() at ${address}`);
send('private_key', key);
} catch (e) {
print(Level.ERROR, `${e.message} (${address})`);
}
}
}
}
}
});
}
const a2bs = (bytes) => Array.from(bytes).map(byte => String.fromCharCode(byte)).join('');
const getKeyLength = (key) => {
let pos = 1; // Skip the initial tag
// Extract length byte, ignoring the long-form indicator bit
let lengthByte = key.charCodeAt(pos++) & 0x7F;
// If lengthByte indicates a short form, return early.
/*
if (lengthByte < 0x80) {
return pos + lengthByte;
}
*/
// For long-form, calculate the length value.
let lengthValue = 0;
for (let i = 0; i < lengthByte; i++) {
lengthValue = (lengthValue << 8) + key.charCodeAt(pos++);
}
return pos + Math.abs(lengthValue);
}
// Exposing functions for RPC calls.
rpc.exports = {
getlibrary: getLibrary,
hooklibrary: hookLibrary
};

60
extractor/vendor.py Normal file
View File

@ -0,0 +1,60 @@
from __future__ import annotations
import logging
logger = logging.getLogger('Vendor')
class Vendor:
"""
Represents Widevine DRM Vendor details for different Android SDK versions.
"""
# https://developer.android.com/tools/releases/platforms
SDK_VERSIONS = {
34: (18, '18.0.0', 'android.hardware.drm-service.widevine', 'android.hardware.drm-service.widevine'),
33: (17, '17.0.0', 'android.hardware.drm-service.widevine', 'libwvaidl.so'),
32: (16, '16.1.0', 'android.hardware.drm@1.4-service.widevine', 'libwvhidl.so'),
31: (16, '16.1.0', 'android.hardware.drm@1.4-service.widevine', 'libwvhidl.so'),
30: (16, '16.0.0', 'android.hardware.drm@1.3-service.widevine', 'libwvhidl.so'),
29: (15, '15.0.0', 'android.hardware.drm@1.2-service.widevine', 'libwvhidl.so'),
28: (14, '14.0.0', 'android.hardware.drm@1.1-service.widevine', 'libwvhidl.so'),
27: (13, '5.1.0', 'android.hardware.drm@1.0-service.widevine', 'libwvhidl.so'),
26: (13, '1.0', 'android.hardware.drm@1.0-service.widevine', 'libwvhidl.so'),
25: (11, '1.0', 'mediadrmserver', 'libwvdrmengine.so'),
24: (11, '1.0', 'mediadrmserver', 'libwvdrmengine.so'),
23: (11, '1.0', 'mediaserver', 'libwvdrmengine.so')
}
def __init__(self, oem: int, version: str, process: str, library: str):
"""
Initialize a Vendor instance.
:param oem: OEM Crypto API level.
:param version: Widevine CDM version.
:param process: The process name associated with the Widevine DRM.
:param library: The library file name used by the DRM process.
"""
self.oem = oem
self.version = version
self.process = process
self.library = library
@classmethod
def from_sdk_api(cls, sdk_api: int) -> Vendor:
"""
Creates a Vendor instance based on the Android SDK API level.
:param sdk_api: Android SDK API level.
:return: A Vendor instance with DRM details.
"""
assert sdk_api > 22, 'Widevine not implemented for SDK <= 22'
vendor_details = cls.SDK_VERSIONS.get(sdk_api)
if not vendor_details:
vendor_details = cls.SDK_VERSIONS[max(cls.SDK_VERSIONS.keys())]
logger.warning('CMD version is not yet implemented')
logger.warning('Using closest supported CDM version: %s', vendor_details[1])
else:
logger.info('CDM version: %s' % vendor_details[1])
logger.info('OEM Crypto API: %s' % vendor_details[0])
return cls(*vendor_details)

45
keydive.py Normal file
View File

@ -0,0 +1,45 @@
import argparse
import logging
import time
import coloredlogs
from _frida import Process
from extractor.cdm import Cdm
coloredlogs.install(
fmt='%(asctime)s [%(levelname).1s] %(name)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=logging.DEBUG)
if __name__ == '__main__':
logger = logging.getLogger('KeyDive')
# Parse command line arguments for device ID
parser = argparse.ArgumentParser(description='Extract Widevine L3 keys from an Android device.')
parser.add_argument('--device', type=str, help='Target Android device ID.')
args = parser.parse_args()
try:
# Initialize CDM handler with given device
cdm = Cdm(device=args.device)
# Find Widevine process on the device
process: Process = next((p for p in cdm.device.enumerate_processes() if cdm.vendor.process == p.name), None)
if not process:
raise Exception('Failed to find Widevine process')
logger.info('Process: %s (%s)', process.pid, process.name)
# Hook into the process to extract DRM keys
if not cdm.hook_process(process):
raise Exception('Failed to hook into the process')
logger.info('Successfully hooked. To test, play a DRM-protected video: https://bitmovin.com/demos/drm')
# Keep script running while extracting keys
while cdm.running:
time.sleep(1)
except KeyboardInterrupt:
pass
except Exception as e:
logger.critical(e)
logger.info('Exiting')

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
frida~=16.1.4
pathlib~=1.0.1
coloredlogs~=15.0.1
pycryptodomex~=3.20.0
protobuf~=4.25.1