diff --git a/pywidevine/cdm.py b/pywidevine/cdm.py index faacb1d..e3212cf 100644 --- a/pywidevine/cdm.py +++ b/pywidevine/cdm.py @@ -235,7 +235,7 @@ class Cdm: def get_license_challenge( self, session_id: bytes, - init_data: Union[Container, bytes, str], + pssh: PSSH, type_: Union[int, str] = LicenseType.STREAMING, privacy_mode: bool = True ) -> bytes: @@ -244,8 +244,7 @@ class Cdm: Parameters: session_id: Session identifier. - init_data: Widevine Cenc Header (Init Data) or a Protection System Specific - Header Box to take the init data from. + pssh: PSSH Object to get the init data from. type_: Type of License you wish to exchange, often `STREAMING`. The `OFFLINE` Licenses are for Offline licensing of Downloaded content. privacy_mode: Encrypt the Client ID using the Privacy Certificate. If the @@ -265,12 +264,10 @@ class Cdm: if not session: raise InvalidSession(f"Session identifier {session_id!r} is invalid.") - if not init_data: - raise InvalidInitData("The init_data must not be empty.") - try: - init_data = PSSH(init_data).init_data - except (ValueError, binascii.Error, DecodeError) as e: - raise InvalidInitData(str(e)) + if not pssh: + raise InvalidInitData("A pssh must be provided.") + if not isinstance(pssh, PSSH): + raise InvalidInitData(f"Expected pssh to be a {PSSH}, not {pssh!r}") try: if isinstance(type_, int): @@ -290,7 +287,9 @@ class Cdm: license_request.protocol_version = ProtocolVersion.Value("VERSION_2_1") license_request.key_control_nonce = random.randrange(1, 2 ** 31) - license_request.content_id.widevine_pssh_data.pssh_data.append(init_data) + # pssh_data may be either a WidevineCencHeader or custom data + # we have to assume the pssh.init_data value is valid, we cannot test + license_request.content_id.widevine_pssh_data.pssh_data.append(pssh.init_data) license_request.content_id.widevine_pssh_data.license_type = type_ license_request.content_id.widevine_pssh_data.request_id = request_id diff --git a/pywidevine/main.py b/pywidevine/main.py index f52b3be..614d673 100644 --- a/pywidevine/main.py +++ b/pywidevine/main.py @@ -13,6 +13,7 @@ from pywidevine import __version__ from pywidevine.cdm import Cdm from pywidevine.device import Device from pywidevine.license_protocol_pb2 import LicenseType, FileHashes +from pywidevine.pssh import PSSH @click.group(invoke_without_command=True) @@ -62,6 +63,9 @@ def license_(device: Path, pssh: str, server: str, type_: str, privacy: bool): """ log = logging.getLogger("license") + # prepare pssh + pssh = PSSH(pssh) + # load device device = Device.load(device) log.info(f"[+] Loaded Device ({device.system_id} L{device.security_level})") diff --git a/pywidevine/remotecdm.py b/pywidevine/remotecdm.py index 3842902..f2df64a 100644 --- a/pywidevine/remotecdm.py +++ b/pywidevine/remotecdm.py @@ -153,7 +153,7 @@ class RemoteCdm(Cdm): def get_license_challenge( self, session_id: bytes, - init_data: Union[Container, bytes, str], + pssh: PSSH, type_: Union[int, str] = LicenseType.STREAMING, privacy_mode: bool = True ) -> bytes: @@ -161,12 +161,10 @@ class RemoteCdm(Cdm): if not session: raise InvalidSession(f"Session identifier {session_id!r} is invalid.") - if not init_data: - raise InvalidInitData("The init_data must not be empty.") - try: - init_data = PSSH(init_data).init_data - except (ValueError, binascii.Error, DecodeError) as e: - raise InvalidInitData(str(e)) + if not pssh: + raise InvalidInitData("A pssh must be provided.") + if not isinstance(pssh, PSSH): + raise InvalidInitData(f"Expected pssh to be a {PSSH}, not {pssh!r}") try: if isinstance(type_, int): @@ -184,7 +182,7 @@ class RemoteCdm(Cdm): url=f"{self.host}/{self.device_name}/challenge/{type_}", json={ "session_id": session_id.hex(), - "init_data": base64.b64encode(init_data).decode() + "init_data": pssh.dumps() } ) if r.status_code != 200: diff --git a/pywidevine/serve.py b/pywidevine/serve.py index 6d47791..148011b 100644 --- a/pywidevine/serve.py +++ b/pywidevine/serve.py @@ -3,6 +3,8 @@ import sys from pathlib import Path from typing import Optional, Union +from pywidevine.pssh import PSSH + try: from aiohttp import web except ImportError: @@ -210,10 +212,13 @@ async def challenge(request: web.Request) -> web.Response: "message": "No Service Certificate set but Privacy Mode is Enforced." }, status=403) + # get init data + init_data = PSSH(body["init_data"]) + # get challenge license_request = cdm.get_license_challenge( session_id=session_id, - init_data=body["init_data"], + pssh=init_data, type_=LicenseType.Value(request.match_info["license_type"]), privacy_mode=True )