CDRM-Project/routes/remote_device_pr.py

294 lines
9.9 KiB
Python

import base64
from flask import Blueprint, jsonify, request, current_app, Response
import os
import yaml
from pyplayready.device import Device as PlayReadyDevice
from pyplayready.cdm import Cdm as PlayReadyCDM
from pyplayready import PSSH as PlayReadyPSSH
from pyplayready.exceptions import (
InvalidSession,
TooManySessions,
InvalidLicense,
InvalidPssh,
)
from custom_functions.database.user_db import fetch_username_by_api_key
from custom_functions.decrypt.api_decrypt import is_base64
from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
from pathlib import Path
remotecdm_pr_bp = Blueprint("remotecdm_pr", __name__)
with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
config = yaml.safe_load(file)
@remotecdm_pr_bp.route("/remotecdm/playready", methods=["GET", "HEAD"])
def remote_cdm_playready():
if request.method == "GET":
return jsonify({"message": "OK"})
if request.method == "HEAD":
response = Response(status=200)
response.headers["Server"] = "playready serve"
return response
@remotecdm_pr_bp.route("/remotecdm/playready/deviceinfo", methods=["GET"])
def remote_cdm_playready_deviceinfo():
base_name = config["default_pr_cdm"]
if not base_name.endswith(".prd"):
full_file_name = base_name + ".prd"
device = PlayReadyDevice.load(f"{os.getcwd()}/configs/CDMs/PR/{full_file_name}")
cdm = PlayReadyCDM.from_device(device)
return jsonify(
{
"security_level": cdm.security_level,
"host": f'{config["fqdn"]}/remotecdm/playready',
"secret": f'{config["remote_cdm_secret"]}',
"device_name": Path(base_name).stem,
}
)
@remotecdm_pr_bp.route("/remotecdm/playready/deviceinfo/<device>", methods=["GET"])
def remote_cdm_playready_deviceinfo_specific(device):
if request.method == "GET":
base_name = Path(device).with_suffix(".prd").name
api_key = request.headers["X-Secret-Key"]
username = fetch_username_by_api_key(api_key)
device = PlayReadyDevice.load(
f"{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}"
)
cdm = PlayReadyCDM.from_device(device)
return jsonify(
{
"security_level": cdm.security_level,
"host": f'{config["fqdn"]}/remotecdm/widevine',
"secret": f"{api_key}",
"device_name": Path(base_name).stem,
}
)
@remotecdm_pr_bp.route("/remotecdm/playready/<device>/open", methods=["GET"])
def remote_cdm_playready_open(device):
if str(device).lower() == config["default_pr_cdm"].lower():
pr_device = PlayReadyDevice.load(
f'{os.getcwd()}/configs/CDMs/PR/{config["default_pr_cdm"]}.prd'
)
cdm = current_app.config["CDM"] = PlayReadyCDM.from_device(pr_device)
session_id = cdm.open()
return jsonify(
{
"message": "Success",
"data": {
"session_id": session_id.hex(),
"device": {"security_level": cdm.security_level},
},
}
)
if (
request.headers["X-Secret-Key"]
and str(device).lower() != config["default_pr_cdm"].lower()
):
api_key = request.headers["X-Secret-Key"]
user = fetch_username_by_api_key(api_key=api_key)
if user:
if user_allowed_to_use_device(device=device, username=user):
pr_device = PlayReadyDevice.load(
f"{os.getcwd()}/configs/CDMs/{user}/PR/{device}.prd"
)
cdm = current_app.config["CDM"] = PlayReadyCDM.from_device(pr_device)
session_id = cdm.open()
return jsonify(
{
"message": "Success",
"data": {
"session_id": session_id.hex(),
"device": {"security_level": cdm.security_level},
},
}
)
else:
return (
jsonify(
{
"message": f"Device '{device}' is not found or you are not authorized to use it.",
}
),
403,
)
else:
return (
jsonify(
{
"message": f"Device '{device}' is not found or you are not authorized to use it.",
}
),
403,
)
else:
return (
jsonify(
{
"message": f"Device '{device}' is not found or you are not authorized to use it.",
}
),
403,
)
@remotecdm_pr_bp.route(
"/remotecdm/playready/<device>/close/<session_id>", methods=["GET"]
)
def remote_cdm_playready_close(device, session_id):
try:
session_id = bytes.fromhex(session_id)
cdm = current_app.config["CDM"]
if not cdm:
return (
jsonify(
{
"message": f'No CDM for "{device}" has been opened yet. No session to close'
}
),
400,
)
try:
cdm.close(session_id)
except InvalidSession:
return (
jsonify(
{
"message": f'Invalid session ID "{session_id.hex()}", it may have expired'
}
),
400,
)
return (
jsonify(
{
"message": f'Successfully closed Session "{session_id.hex()}".',
}
),
200,
)
except Exception as e:
return (
jsonify({"message": f'Failed to close Session "{session_id.hex()}".'}),
400,
)
@remotecdm_pr_bp.route(
"/remotecdm/playready/<device>/get_license_challenge", methods=["POST"]
)
def remote_cdm_playready_get_license_challenge(device):
body = request.get_json()
for required_field in ("session_id", "init_data"):
if not body.get(required_field):
return (
jsonify(
{
"message": f'Missing required field "{required_field}" in JSON body'
}
),
400,
)
cdm = current_app.config["CDM"]
session_id = bytes.fromhex(body["session_id"])
init_data = body["init_data"]
if not init_data.startswith("<WRMHEADER"):
try:
pssh = PlayReadyPSSH(init_data)
if pssh.wrm_headers:
init_data = pssh.wrm_headers[0]
except InvalidPssh as e:
return jsonify({"message": f"Unable to parse base64 PSSH, {e}"})
try:
license_request = cdm.get_license_challenge(
session_id=session_id, wrm_header=init_data
)
except InvalidSession:
return jsonify(
{
"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
}
)
except Exception as e:
return jsonify({"message": f"Error, {e}"})
return jsonify({"message": "success", "data": {"challenge": license_request}})
@remotecdm_pr_bp.route("/remotecdm/playready/<device>/parse_license", methods=["POST"])
def remote_cdm_playready_parse_license(device):
body = request.get_json()
for required_field in ("license_message", "session_id"):
if not body.get(required_field):
return jsonify(
{"message": f'Missing required field "{required_field}" in JSON body'}
)
cdm = current_app.config["CDM"]
if not cdm:
return jsonify(
{
"message": f"No Cdm session for {device} has been opened yet. No session to use."
}
)
session_id = bytes.fromhex(body["session_id"])
license_message = body["license_message"]
if is_base64(license_message):
license_message = base64.b64decode(license_message).decode("utf-8")
try:
cdm.parse_license(session_id, license_message)
except InvalidSession:
return jsonify(
{
"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
}
)
except InvalidLicense as e:
return jsonify({"message": f"Invalid License, {e}"})
except Exception as e:
return jsonify({"message": f"Error, {e}"})
return jsonify(
{"message": "Successfully parsed and loaded the Keys from the License message"}
)
@remotecdm_pr_bp.route("/remotecdm/playready/<device>/get_keys", methods=["POST"])
def remote_cdm_playready_get_keys(device):
body = request.get_json()
for required_field in ("session_id",):
if not body.get(required_field):
return jsonify(
{"message": f'Missing required field "{required_field}" in JSON body'}
)
session_id = bytes.fromhex(body["session_id"])
cdm = current_app.config["CDM"]
if not cdm:
return jsonify(
{"message": f"Missing required field '{required_field}' in JSON body."}
)
try:
keys = cdm.get_keys(session_id)
except InvalidSession:
return jsonify(
{
"message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
}
)
except Exception as e:
return jsonify({"message": f"Error, {e}"})
keys_json = [
{
"key_id": key.key_id.hex,
"key": key.key.hex(),
"type": key.key_type.value,
"cipher_type": key.cipher_type.value,
"key_length": key.key_length,
}
for key in keys
]
return jsonify({"message": "success", "data": {"keys": keys_json}})