|
|
@ -7,9 +7,9 @@ import binascii |
|
|
|
from google.protobuf.message import DecodeError |
|
|
|
from google.protobuf.message import DecodeError |
|
|
|
from google.protobuf import text_format |
|
|
|
from google.protobuf import text_format |
|
|
|
|
|
|
|
|
|
|
|
from pywidevine.L3.cdm.formats import wv_proto2_pb2 as wv_proto2 |
|
|
|
from pywidevineb.L3.cdm.formats import wv_proto2_pb2 as wv_proto2 |
|
|
|
from pywidevine.L3.cdm.session import Session |
|
|
|
from pywidevineb.L3.cdm.session import Session |
|
|
|
from pywidevine.L3.cdm.key import Key |
|
|
|
from pywidevineb.L3.cdm.key import Key |
|
|
|
from Cryptodome.Random import get_random_bytes |
|
|
|
from Cryptodome.Random import get_random_bytes |
|
|
|
from Cryptodome.Random import random |
|
|
|
from Cryptodome.Random import random |
|
|
|
from Cryptodome.Cipher import PKCS1_OAEP, AES |
|
|
|
from Cryptodome.Cipher import PKCS1_OAEP, AES |
|
|
@ -19,12 +19,13 @@ from Cryptodome.Signature import pss |
|
|
|
from Cryptodome.Util import Padding |
|
|
|
from Cryptodome.Util import Padding |
|
|
|
import logging |
|
|
|
import logging |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Cdm: |
|
|
|
class Cdm: |
|
|
|
def __init__(self): |
|
|
|
def __init__(self): |
|
|
|
self.logger = logging.getLogger(__name__) |
|
|
|
self.logger = logging.getLogger(__name__) |
|
|
|
self.sessions = {} |
|
|
|
self.sessions = {} |
|
|
|
|
|
|
|
|
|
|
|
def open_session(self, init_data_b64, device, raw_init_data = None, offline=False): |
|
|
|
def open_session(self, init_data_b64, device, raw_init_data=None, offline=False): |
|
|
|
self.logger.debug("open_session(init_data_b64={}, device={}".format(init_data_b64, device)) |
|
|
|
self.logger.debug("open_session(init_data_b64={}, device={}".format(init_data_b64, device)) |
|
|
|
self.logger.info("opening new cdm session") |
|
|
|
self.logger.info("opening new cdm session") |
|
|
|
if device.session_id_type == 'android': |
|
|
|
if device.session_id_type == 'android': |
|
|
@ -145,14 +146,9 @@ class Cdm: |
|
|
|
else: |
|
|
|
else: |
|
|
|
license_request = wv_proto2.SignedLicenseRequest() |
|
|
|
license_request = wv_proto2.SignedLicenseRequest() |
|
|
|
client_id = wv_proto2.ClientIdentification() |
|
|
|
client_id = wv_proto2.ClientIdentification() |
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(session.device_config.device_client_id_blob_filename): |
|
|
|
|
|
|
|
self.logger.error("no client ID blob available for this device") |
|
|
|
|
|
|
|
return 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with open(session.device_config.device_client_id_blob_filename, "rb") as f: |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
cid_bytes = client_id.ParseFromString(f.read()) |
|
|
|
cid_bytes = client_id.ParseFromString(base64.b64decode( |
|
|
|
|
|
|
|
"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")) |
|
|
|
except DecodeError: |
|
|
|
except DecodeError: |
|
|
|
self.logger.error("client id failed to parse as protobuf") |
|
|
|
self.logger.error("client id failed to parse as protobuf") |
|
|
|
return 1 |
|
|
|
return 1 |
|
|
@ -175,7 +171,7 @@ class Cdm: |
|
|
|
license_request.Msg.RequestTime = int(time.time()) |
|
|
|
license_request.Msg.RequestTime = int(time.time()) |
|
|
|
license_request.Msg.ProtocolVersion = wv_proto2.ProtocolVersion.Value('CURRENT') |
|
|
|
license_request.Msg.ProtocolVersion = wv_proto2.ProtocolVersion.Value('CURRENT') |
|
|
|
if session.device_config.send_key_control_nonce: |
|
|
|
if session.device_config.send_key_control_nonce: |
|
|
|
license_request.Msg.KeyControlNonce = random.randrange(1, 2**31) |
|
|
|
license_request.Msg.KeyControlNonce = random.randrange(1, 2 ** 31) |
|
|
|
|
|
|
|
|
|
|
|
if session.privacy_mode: |
|
|
|
if session.privacy_mode: |
|
|
|
if session.device_config.vmp: |
|
|
|
if session.device_config.vmp: |
|
|
@ -218,8 +214,36 @@ class Cdm: |
|
|
|
else: |
|
|
|
else: |
|
|
|
license_request.Msg.ClientId.CopyFrom(client_id) |
|
|
|
license_request.Msg.ClientId.CopyFrom(client_id) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
kes="""-----BEGIN RSA PRIVATE KEY----- |
|
|
|
|
|
|
|
MIIEpAIBAAKCAQEAosIRKPWQ6+vptCMzSpZ4b4v5g8kp+qs2BgOepFOJG6Tuj1Ph |
|
|
|
|
|
|
|
MzsK2ykmygErTW2X9PF610gjmCREslYzbcVdJ99+BAI1Yp18fFJCoOqYRUrs0mcb |
|
|
|
|
|
|
|
1GaVuhU+hlml2AE8jOvHl/33fJir4Cws14/y680MGOJYPtHtYnvFrMUmMAReqlLs |
|
|
|
|
|
|
|
HMvyMdGLR3oSOS35+xnh1Kuztrlw/KVlGSANFQ0bYMQEy6C4jC4aFu9IUMj+UPcG |
|
|
|
|
|
|
|
pJ07t8cCPXh7eE1zb3SJuYvFqZKMT7OnethnaiDAE4xqQws5z6Wjrxmgop/Oz8hO |
|
|
|
|
|
|
|
048G/jrNoDnfBK2nsWT+4EbuwFZTWsK28WyEdwIDAQABAoIBACkwyDr/ev/aIrlO |
|
|
|
|
|
|
|
N0rnLe+9ExbBMHiaIAABpoKcCZUPdribV+EpTiQNFB4Hkbf0xoJdIuOdBDUa6K/h |
|
|
|
|
|
|
|
lP5w9pSCwVeuX2hnxvuHrgkflg3jWnAdXDOzCq2fdsV1pr02Aub/PPJAegP0d3sy |
|
|
|
|
|
|
|
ct7TNX1r1WXu0rqDUnqcLHj/JBz+drkyfcLOLbKffd98t1Sxsjy/aFMiUngHk/uj |
|
|
|
|
|
|
|
imNamAMNhmob2xyah8pqg7Y7XFuZn3Wu+i+tL2HoAZRUaRWXiBPV1SST0F/4pQg5 |
|
|
|
|
|
|
|
we9xaMfuxuwBIdRPkiiyagK1IWqT2XsVG2byMEvcq3iIVyAS1dzb85tZbdv+ufR4 |
|
|
|
|
|
|
|
VoZ70lECgYEA1CvxfdmuqBG45GKQzin5+jnkGLnj6LjEH5EtUQPy2Is3N6WJIp6B |
|
|
|
|
|
|
|
SHxgddoJZh3Pc9D62nKLTrAkkk1UpCrrFXpjy7VkIUBnEzVj0Nbh0xoV9brRPQOD |
|
|
|
|
|
|
|
lqtrfj1NQNNY8ZgWpILnJ9n26Gqjr2nkUlAsu3bPaz/VzffA/waP4pUCgYEAxGEJ |
|
|
|
|
|
|
|
MO99eIpkZdZU7PxjRs8rJmIzx77MekWpUJKtKzDA6BbwWI2oLuG9zbcANMKMdonD |
|
|
|
|
|
|
|
j2ZXdVUQfqBvcwHuDmK+7FhKQ1Rw0jWWlrEADYQgK3MfqMPoOGv9Wn3hrBetSbWK |
|
|
|
|
|
|
|
HTXOQQccDaEzSSCTOG3RPrMi2eIp7uFCENbqM9sCgYB4mdHW+1kv54L1LqGozmtt |
|
|
|
|
|
|
|
NGLXOzK1IfE5EEh1+IydUeS9GLbumrJaBXi/BIS7Ks60wmEUsm9E9xKSpqop9stR |
|
|
|
|
|
|
|
lhQLwrt7uyPb40kteDc8y2MYHmy5BbpSdnXPeADljDzOdujH8jB6koaqbZNFLie+ |
|
|
|
|
|
|
|
Mhx7InmcONjLDr0BOTWoUQKBgQCAzkzjBhK8P7m+eijWEG1lgnkBAiSIfYNNJ+f4 |
|
|
|
|
|
|
|
a1yeGapOEM2wp6mKppKCHehKstjC33Wf1zbCRPs+syimvLtSQD6OcxKyuu4NUwzk |
|
|
|
|
|
|
|
5k/sjZ80IJzBa04jw+E3u52L7TPCRwrCQgp46Jrj7bnf2zf1KUK353OSih+LCcD1 |
|
|
|
|
|
|
|
nqGbRQKBgQDC5ns0X8TnJCgf1BD3cGvc3o9zo3gw/NuZ6cqm8q45u0kiw5pRs+7j |
|
|
|
|
|
|
|
9CXENirhHL5JXighOFB78Q3WWMuppTDxj7S1rpYdgp6+ITGSOmY5Xs6uaimilt2H |
|
|
|
|
|
|
|
JPmXCYQt2Qu51bJ+MqZRWYeyN01O6rdKX/zGD9UTN5D3Ty3KEzogkg== |
|
|
|
|
|
|
|
-----END RSA PRIVATE KEY-----""" |
|
|
|
|
|
|
|
|
|
|
|
if session.device_config.private_key_available: |
|
|
|
if session.device_config.private_key_available: |
|
|
|
key = RSA.importKey(open(session.device_config.device_private_key_filename).read()) |
|
|
|
key = RSA.importKey(kes) |
|
|
|
session.device_key = key |
|
|
|
session.device_key = key |
|
|
|
else: |
|
|
|
else: |
|
|
|
self.logger.error("need device private key, other methods unimplemented") |
|
|
|
self.logger.error("need device private key, other methods unimplemented") |
|
|
@ -318,7 +342,8 @@ class Cdm: |
|
|
|
lic_hmac = HMAC.new(session.derived_keys['auth_1'], digestmod=SHA256) |
|
|
|
lic_hmac = HMAC.new(session.derived_keys['auth_1'], digestmod=SHA256) |
|
|
|
lic_hmac.update(license.Msg.SerializeToString()) |
|
|
|
lic_hmac.update(license.Msg.SerializeToString()) |
|
|
|
|
|
|
|
|
|
|
|
self.logger.debug("calculated sig: {} actual sig: {}".format(lic_hmac.hexdigest(), binascii.hexlify(license.Signature))) |
|
|
|
self.logger.debug( |
|
|
|
|
|
|
|
"calculated sig: {} actual sig: {}".format(lic_hmac.hexdigest(), binascii.hexlify(license.Signature))) |
|
|
|
|
|
|
|
|
|
|
|
if lic_hmac.digest() != license.Signature: |
|
|
|
if lic_hmac.digest() != license.Signature: |
|
|
|
self.logger.info("license signature doesn't match - writing bin so they can be debugged") |
|
|
|
self.logger.info("license signature doesn't match - writing bin so they can be debugged") |
|
|
|