import pprint
import random
from StringIO import StringIO
-from hmac import HMAC
-import hashlib
import requests
import zlib
import time
-from Crypto.PublicKey import RSA
-from Crypto.Cipher import PKCS1_OAEP
-from Crypto.Cipher import AES
-from Crypto.Random import get_random_bytes
-# from Crypto.Hash import HMAC, SHA256
-from Crypto.Util import Padding
+from Cryptodome.PublicKey import RSA
+from Cryptodome.Cipher import PKCS1_OAEP
+from Cryptodome.Cipher import AES
+from Cryptodome.Random import get_random_bytes
+from Crypto.Hash import HMAC, SHA256
+from Cryptodome.Util import Padding
import xml.etree.ElementTree as ET
pp = pprint.PrettyPrinter(indent=4)
pass
if self.file_exists(self.kodi_helper.msl_data_path, 'msl_data.json'):
+ self.kodi_helper.log(msg='MSL Data exists. Use old Tokens.')
self.__load_msl_data()
self.handshake_performed = True
elif self.file_exists(self.kodi_helper.msl_data_path, 'rsa_key.bin'):
self.__perform_key_handshake()
def load_manifest(self, viewable_id):
+ """
+ Loads the manifets for the given viewable_id and returns a mpd-XML-Manifest
+ :param viewable_id: The id of of the viewable
+ :return: MPD XML Manifest or False if no success
+ """
manifest_request_data = {
'method': 'manifest',
'lookupType': 'PREPARE',
'uiVersion': 'akira'
}
request_data = self.__generate_msl_request_data(manifest_request_data)
-
resp = self.session.post(self.endpoints['manifest'], request_data)
-
try:
+ # if the json() does not fail we have an error because the manifest response is a chuncked json response
resp.json()
- self.kodi_helper.log(msg='MANIFEST RESPONE JSON: '+resp.text)
+ self.kodi_helper.log(msg='Error getting Manifest: '+resp.text)
+ return False
except ValueError:
- # Maybe we have a CHUNKED response
+ # json() failed so parse the chunked response
+ self.kodi_helper.log(msg='Got chunked Manifest Response: ' + resp.text)
resp = self.__parse_chunked_msl_response(resp.text)
+ self.kodi_helper.log(msg='Parsed chunked Response: ' + json.dumps(resp))
data = self.__decrypt_payload_chunk(resp['payloads'][0])
- # pprint.pprint(data)
return self.__tranform_to_dash(data)
-
def get_license(self, challenge, sid):
-
"""
- std::time_t t = std::time(0); // t is an integer type
- licenseRequestData["clientTime"] = (int)t;
- //licenseRequestData["challengeBase64"] = challengeStr;
- licenseRequestData["licenseType"] = "STANDARD";
- licenseRequestData["playbackContextId"] = playbackContextId;//"E1-BQFRAAELEB32o6Se-GFvjwEIbvDydEtfj6zNzEC3qwfweEPAL3gTHHT2V8rS_u1Mc3mw5BWZrUlKYIu4aArdjN8z_Z8t62E5jRjLMdCKMsVhlSJpiQx0MNW4aGqkYz-1lPh85Quo4I_mxVBG5lgd166B5NDizA8.";
- licenseRequestData["drmContextIds"] = Json::arrayValue;
- licenseRequestData["drmContextIds"].append(drmContextId);
-
- :param viewable_id:
- :param challenge:
- :param kid:
- :return:
+ Requests and returns a license for the given challenge and sid
+ :param challenge: The base64 encoded challenge
+ :param sid: The sid paired to the challengew
+ :return: Base64 representation of the license key or False if no success
"""
-
license_request_data = {
'method': 'license',
'licenseType': 'STANDARD',
resp = self.session.post(self.endpoints['license'], request_data)
try:
+ # If is valid json the request for the licnese failed
resp.json()
- self.kodi_helper.log(msg='LICENSE RESPONE JSON: '+resp.text)
+ self.kodi_helper.log(msg='Error getting license: '+resp.text)
+ return False
except ValueError:
- # Maybe we have a CHUNKED response
+ # json() failed so we have a chunked json response
resp = self.__parse_chunked_msl_response(resp.text)
data = self.__decrypt_payload_chunk(resp['payloads'][0])
- # pprint.pprint(data)
if data['success'] is True:
return data['result']['licenses'][0]['data']
else:
- return ''
-
+ self.kodi_helper.log(msg='Error getting license: ' + json.dumps(data))
+ return False
def __decrypt_payload_chunk(self, payloadchunk):
payloadchunk = json.JSONDecoder().decode(payloadchunk)
'signature': self.__sign(first_payload_encryption_envelope),
}
-
- # Create Second Payload
- second_payload = {
- "messageid": self.current_message_id,
- "data": "",
- "endofmsg": True,
- "sequencenumber": 2
- }
- second_payload_encryption_envelope = self.__encrypt(json.dumps(second_payload))
- second_payload_chunk = {
- 'payload': base64.standard_b64encode(second_payload_encryption_envelope),
- 'signature': base64.standard_b64encode(self.__sign(second_payload_encryption_envelope)),
- }
-
- request_data = json.dumps(header) + json.dumps(first_payload_chunk) # + json.dumps(second_payload_chunk)
+ request_data = json.dumps(header) + json.dumps(first_payload_chunk)
return request_data
encryption_envelope['ciphertext'] = base64.standard_b64encode(ciphertext)
return json.dumps(encryption_envelope)
- def __sign(self, text):
- #signature = hmac.new(self.sign_key, text, hashlib.sha256).digest()
- signature = HMAC(self.sign_key, text, hashlib.sha256).digest()
-
- # hmac = HMAC.new(self.sign_key, digestmod=SHA256)
- # hmac.update(text)
+ def __sign(self, text):
+ """
+ Calculates the HMAC signature for the given text with the current sign key and SHA256
+ :param text:
+ :return: Base64 encoded signature
+ """
+ signature = HMAC.new(self.sign_key, text, SHA256).digest()
return base64.standard_b64encode(signature)
def __perform_key_handshake(self):
-
header = self.__generate_msl_header(is_key_request=True, is_handshake=True, compressionalgo="", encrypt=False)
request = {
'entityauthdata': {
self.kodi_helper.log(msg='Key Handshake Request:')
self.kodi_helper.log(msg=json.dumps(request))
-
resp = self.session.post(self.endpoints['manifest'], json.dumps(request, sort_keys=True))
if resp.status_code == 200:
resp = resp.json()
def __set_master_token(self, master_token):
self.mastertoken = master_token
- self.sequence_number = json.JSONDecoder().decode(base64.standard_b64decode(master_token['tokendata']))[
- 'sequencenumber']
+ self.sequence_number = json.JSONDecoder().decode(base64.standard_b64decode(master_token['tokendata']))['sequencenumber']
def __load_rsa_keys(self):
loaded_key = self.load_file(self.kodi_helper.msl_data_path, 'rsa_key.bin')
from resources.lib.KodiHelper import KodiHelper
from resources.lib.MSLHttpRequestHandler import MSLHttpRequestHandler
-def select_unused_port():
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(('localhost', 0))
- addr, port = s.getsockname()
- s.close()
- return port
-
addon = xbmcaddon.Addon()
kodi_helper = KodiHelper(
plugin_handle=None,
base_url=None
)
-PORT = select_unused_port()
-addon.setSetting('msl_service_port', str(PORT))
-kodi_helper.log(msg='Picked Port: ' + str(PORT))
-Handler = MSLHttpRequestHandler
+
+def select_unused_port():
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(('localhost', 0))
+ addr, port = sock.getsockname()
+ sock.close()
+ return port
+
+port = select_unused_port()
+addon.setSetting('msl_service_port', str(port))
+kodi_helper.log(msg='Picked Port: ' + str(port))
+
+#Config the HTTP Server
SocketServer.TCPServer.allow_reuse_address = True
-server = SocketServer.TCPServer(('127.0.0.1', PORT), Handler)
+server = SocketServer.TCPServer(('127.0.0.1', port), MSLHttpRequestHandler)
server.server_activate()
server.timeout = 1