pywidevine/pywidevine/key.py

67 lines
2.0 KiB
Python

from __future__ import annotations
import base64
from typing import Optional, Union
from uuid import UUID
from Crypto.Cipher import AES
from Crypto.Util import Padding
from pywidevine.license_protocol_pb2 import License
class Key:
def __init__(self, type_: str, kid: UUID, key: bytes, permissions: Optional[list[str]] = None):
self.type = type_
self.kid = kid
self.key = key
self.permissions = permissions or []
def __repr__(self) -> str:
return "{name}({items})".format(
name=self.__class__.__name__,
items=", ".join([f"{k}={repr(v)}" for k, v in self.__dict__.items()])
)
@classmethod
def from_key_container(cls, key: License.KeyContainer, enc_key: bytes) -> Key:
"""Load Key from a KeyContainer object."""
permissions = []
if key.type == License.KeyContainer.KeyType.Value("OPERATOR_SESSION"):
for descriptor, value in key.operator_session_key_permissions.ListFields():
if value == 1:
permissions.append(descriptor.name)
return Key(
type_=License.KeyContainer.KeyType.Name(key.type),
kid=cls.kid_to_uuid(key.id),
key=Padding.unpad(
AES.new(enc_key, AES.MODE_CBC, iv=key.iv).decrypt(key.key),
16
),
permissions=permissions
)
@staticmethod
def kid_to_uuid(kid: Union[str, bytes]) -> UUID:
"""
Convert a Key ID from a string or bytes to a UUID object.
At first this may seem very simple but some types of Key IDs
may not be 16 bytes and some may be decimal vs. hex.
"""
if isinstance(kid, str):
kid = base64.b64decode(kid)
if not kid:
kid = b"\x00" * 16
if kid.decode(errors="replace").isdigit():
return UUID(int=int(kid.decode()))
if len(kid) < 16:
kid += b"\x00" * (16 - len(kid))
return UUID(bytes=kid)
__all__ = ("Key",)