DRMLab
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import base64, json, requests
|
||||
from enum import Enum, IntEnum
|
||||
from hashlib import sha1
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util import Padding
|
||||
|
||||
class TrackType(str, Enum):
|
||||
SD = "SD"
|
||||
HD = "HD"
|
||||
AUDIO = "AUDIO"
|
||||
|
||||
|
||||
class WidevineError(IntEnum):
|
||||
DRM_DEVICE_CERTIFICATE_REVOKED = 127
|
||||
DRM_DEVICE_CERT_SERIAL_REVOKED = 175
|
||||
INVALID_PSSH = 152
|
||||
INVALID_LICENSE_CHALLENGE = 106
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Constants
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
AES_KEY = bytes.fromhex(
|
||||
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9"
|
||||
)
|
||||
AES_IV = bytes.fromhex(
|
||||
"d58ce954203b7c9a9a9d467f59839249"
|
||||
)
|
||||
|
||||
PROVIDER = "widevine_test"
|
||||
CONTENT_ID = "ZmtqM2xqYVNkZmFsa3Izag==" # example content id
|
||||
|
||||
def build_request(challenge) -> dict:
|
||||
"""Build signed Widevine license request payload."""
|
||||
if not isinstance(challenge, str):
|
||||
challenge = base64.b64encode(challenge).decode()
|
||||
|
||||
payload = json.dumps(
|
||||
{
|
||||
"payload": challenge,
|
||||
"provider": PROVIDER,
|
||||
"content_id": CONTENT_ID,
|
||||
"content_key_specs": [{"track_type": t.value} for t in TrackType],
|
||||
},
|
||||
separators=(",", ":"),
|
||||
).encode()
|
||||
|
||||
request_b64 = base64.b64encode(payload).decode()
|
||||
|
||||
# Widevine-style signature
|
||||
digest = sha1(base64.b64decode(request_b64)).digest()
|
||||
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
|
||||
signature = base64.b64encode(cipher.encrypt(Padding.pad(digest, 16))).decode()
|
||||
|
||||
return {
|
||||
"request": request_b64,
|
||||
"signature": signature,
|
||||
"signer": PROVIDER,
|
||||
}
|
||||
|
||||
|
||||
def check_response(resp: dict):
|
||||
"""Check for error status and raise if needed."""
|
||||
code = resp.get("internal_status")
|
||||
if not code:
|
||||
return
|
||||
|
||||
try:
|
||||
error = WidevineError(code)
|
||||
raise RuntimeError(f"License error: {error.name} ({code})")
|
||||
except ValueError:
|
||||
# Unknown code, ignore
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
pssh = "CAESEFoHzpkm/EolkmbSIjR2qgkaCHVzcC1jZW5jIhhXZ2ZPbVNiOFNpV1NadElpTkhhcUNRPT0qADIA"
|
||||
challenge = "CAQ="
|
||||
|
||||
# 1. Request service certificate
|
||||
cert_resp = requests.post(
|
||||
"https://license.widevine.com/cenc/getlicense/widevine_test",
|
||||
json=build_request(challenge),
|
||||
).json()
|
||||
service_cert = cert_resp.get("license")
|
||||
|
||||
challenge = "" # use pywidevine to generate B64 challenge using pssh, service_cert
|
||||
|
||||
# 2. Issue real license request
|
||||
license_resp = requests.post(
|
||||
"https://license.widevine.com/cenc/getlicense/widevine_test",
|
||||
json=build_request(challenge),
|
||||
).json()
|
||||
|
||||
check_response(license_resp)
|
||||
|
||||
print("level:", license_resp.get("security_level"))
|
||||
print("systemID:", license_resp.get("system_id"))
|
||||
print("status:", license_resp.get("device_state"))
|
||||
Reference in New Issue
Block a user