diff --git a/README.md b/README.md deleted file mode 100644 index c0207b2..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# CDRM-Extension-API - -Open source API back end for the popular Google Chrome extension written by Zane. \ No newline at end of file diff --git a/cache_keys.py b/cache_keys.py new file mode 100644 index 0000000..778453e --- /dev/null +++ b/cache_keys.py @@ -0,0 +1,12 @@ +# Import dependencies +import sqlite3 +import os + + +# Define cache function +def cache_keys(pssh: str, keys: str, mpd: str): + dbconnection = sqlite3.connect(f"{os.getcwd()}/keys/database.db") + dbcursor = dbconnection.cursor() + dbcursor.execute("INSERT or REPLACE INTO database VALUES (?, ?, ?)", (pssh, keys, mpd)) + dbconnection.commit() + dbconnection.close() \ No newline at end of file diff --git a/database_check.py b/database_check.py new file mode 100644 index 0000000..1145274 --- /dev/null +++ b/database_check.py @@ -0,0 +1,17 @@ +# Import dependencies +import os +import sqlite3 + + +# Check to see if the database already exists, if not create a keys folder, and create the database. +def database_check(): + # Check to see if the "keys" directory exists, if not creates it + if "keys" not in os.listdir(os.getcwd()): + os.makedirs('keys') + + # Check to see if a database exists in keys directory, if not create it + if not os.path.isfile(f"{os.getcwd()}/keys/database.db"): + dbconnection = sqlite3.connect(f"{os.getcwd()}/keys/database.db") + dbcursor = dbconnection.cursor() + dbcursor.execute('CREATE TABLE IF NOT EXISTS "DATABASE" ( "pssh" TEXT, "keys" TEXT, "mpd" TEXT, PRIMARY KEY("pssh") )') + dbconnection.close() \ No newline at end of file diff --git a/decrypt.py b/decrypt.py new file mode 100644 index 0000000..1e78860 --- /dev/null +++ b/decrypt.py @@ -0,0 +1,79 @@ +# import dependencies +import os +from pywidevine import PSSH +from pywidevine import Cdm +from pywidevine import Device +import requests +import glob +from cache_keys import cache_keys + +# Get the current working directory +main_directory = os.getcwd() + +# Making sure a .wvd file exists and using that as the extracted device +try: + extracted_device = glob.glob(f'{main_directory}/*.wvd')[0] +except: + extracted_cdm = None + print(f"Please place a WVD in {main_directory}/") + + +# Defining decrypt function +def decrypt_content(in_pssh: str = None, license_url: str = None, headers: dict = None, mpd: str = None) -> list: + + # prepare pssh + pssh = PSSH(in_pssh) + + # load device + device = Device.load(extracted_device) + + # load CDM from device + cdm = Cdm.from_device(device) + + # open CDM session + session_id = cdm.open() + + # Generate the challenge + challenge = cdm.get_license_challenge(session_id, pssh) + + # send license challenge + license = requests.post( + url=license_url, + headers=headers, + data=challenge + ) + + # Parse the license if it comes back in plain bytes + try: + cdm.parse_license(session_id, license.content) + except: + # Exception, try to find by regex via json + try: + cdm.parse_license(session_id, license.json()['license']) + except: + try: + cdm.parse_license(session_id, license.json()['licenseData']) + except Exception as error: + return [error] + + + # Assign variable for caching keys + cached_keys = "" + + # assign variable for returned keys + returned_keys = [] + + + for key in cdm.get_keys(session_id): + if key.type != "SIGNING": + returned_keys.append(f"--key {key.kid.hex}:{key.key.hex()}") + cached_keys += f"{key.kid.hex}:{key.key.hex()}\n" + + # Cache the keys + cache_keys(pssh=in_pssh, keys=cached_keys, mpd=mpd) + + # close session, disposes of session data + cdm.close(session_id) + + # Return the keys + return returned_keys diff --git a/main.py b/main.py new file mode 100644 index 0000000..80229ef --- /dev/null +++ b/main.py @@ -0,0 +1,53 @@ +from decrypt import decrypt_content +import json +from flask import Flask, request +from database_check import database_check + +app = Flask(__name__) + +database_check() + + +@app.route('/', methods=['POST']) +def post_endpoint(): + + # Get the JSON data from the request + data = json.loads(request.data.decode()) + print(json.dumps(data, indent=4)) + + # Get the PSSH + pssh = data['pssh'] + + # Get the license URL + lic_url = data['license_url'] + + # Get the headers + headers = data['headers'] + + # Get the MPD url + mpd = data['manifest_url'] + + # Format the headers + + # Split the string into lines + lines = headers.strip().split('\n') + + # Create an empty dictionary to store the key-value pairs + headers_dict = {} + + # Iterate through each line and split it into key-value pairs + for line in lines: + key, value = line.split(': ', 1) # Split only at the first occurrence of ': ' to handle values containing ': ' + headers_dict[key] = value + + print(json.dumps(headers_dict, indent=4)) + + try: + keys = decrypt_content(in_pssh=pssh, license_url=lic_url, headers=headers_dict, mpd=mpd) + return {"keys": keys} + except Exception as error: + return {"keys": [f'{error}']} + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c782a37 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask>=3.0.3 +requests>=2.31.0 +pywidevine>=1.8.0 \ No newline at end of file