upload
This commit is contained in:
0
pywidevine/cdm/__init__.py
Normal file
0
pywidevine/cdm/__init__.py
Normal file
BIN
pywidevine/cdm/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/cdm.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/cdm.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/cdm.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/cdm.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/cdm.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/cdm.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/cdm.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/cdm.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/deviceconfig.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/key.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/key.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/key.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/key.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/key.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/key.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/key.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/key.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/session.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/session.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/session.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/session.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/session.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/session.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/__pycache__/session.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/__pycache__/session.cpython-39.pyc
Normal file
Binary file not shown.
407
pywidevine/cdm/cdm.py
Normal file
407
pywidevine/cdm/cdm.py
Normal file
@@ -0,0 +1,407 @@
|
||||
import base64
|
||||
|
||||
import os
|
||||
import time
|
||||
import binascii
|
||||
|
||||
from google.protobuf.message import DecodeError
|
||||
from google.protobuf import text_format
|
||||
|
||||
from pywidevine.cdm.formats import wv_proto2_pb2 as wv_proto2
|
||||
from pywidevine.cdm.session import Session
|
||||
from pywidevine.cdm.key import Key
|
||||
from Cryptodome.Random import get_random_bytes
|
||||
from Cryptodome.Random import random
|
||||
from Cryptodome.Cipher import PKCS1_OAEP, AES
|
||||
from Cryptodome.Hash import CMAC, SHA256, HMAC, SHA1
|
||||
from Cryptodome.PublicKey import RSA
|
||||
from Cryptodome.Signature import pss
|
||||
from Cryptodome.Util import Padding
|
||||
import logging
|
||||
|
||||
|
||||
class Cdm:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.sessions = {}
|
||||
|
||||
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.info("opening new cdm session")
|
||||
if device.session_id_type == "android":
|
||||
# format: 16 random hexdigits, 2 digit counter, 14 0s
|
||||
rand_ascii = "".join(random.choice("ABCDEF0123456789") for _ in range(16))
|
||||
counter = "01" # this resets regularly so its fine to use 01
|
||||
rest = "00000000000000"
|
||||
session_id = rand_ascii + counter + rest
|
||||
session_id = session_id.encode("ascii")
|
||||
elif device.session_id_type == "chrome":
|
||||
rand_bytes = get_random_bytes(16)
|
||||
session_id = rand_bytes
|
||||
else:
|
||||
# other formats NYI
|
||||
self.logger.error("device type is unusable")
|
||||
return 1
|
||||
if raw_init_data and isinstance(raw_init_data, (bytes, bytearray)):
|
||||
# used for NF key exchange, where they don't provide a valid PSSH
|
||||
init_data = raw_init_data
|
||||
self.raw_pssh = True
|
||||
else:
|
||||
init_data = self._parse_init_data(init_data_b64)
|
||||
self.raw_pssh = False
|
||||
|
||||
if init_data:
|
||||
new_session = Session(session_id, init_data, device, offline)
|
||||
else:
|
||||
self.logger.error("unable to parse init data")
|
||||
return 1
|
||||
self.sessions[session_id] = new_session
|
||||
#self.logger.info("session opened and init data parsed successfully")
|
||||
return session_id
|
||||
|
||||
def _parse_init_data(self, init_data_b64):
|
||||
parsed_init_data = wv_proto2.WidevineCencHeader()
|
||||
try:
|
||||
self.logger.debug("trying to parse init_data directly")
|
||||
parsed_init_data.ParseFromString(base64.b64decode(init_data_b64)[32:])
|
||||
except DecodeError:
|
||||
self.logger.debug(
|
||||
"unable to parse as-is, trying with removed pssh box header"
|
||||
)
|
||||
try:
|
||||
id_bytes = parsed_init_data.ParseFromString(
|
||||
base64.b64decode(init_data_b64)[32:]
|
||||
)
|
||||
except DecodeError:
|
||||
self.logger.error("unable to parse, unsupported init data format")
|
||||
return None
|
||||
self.logger.debug("init_data:")
|
||||
for line in text_format.MessageToString(parsed_init_data).splitlines():
|
||||
self.logger.debug(line)
|
||||
return parsed_init_data
|
||||
|
||||
def close_session(self, session_id):
|
||||
self.logger.debug("close_session(session_id={})".format(session_id))
|
||||
#self.logger.info("closing cdm session")
|
||||
if session_id in self.sessions:
|
||||
self.sessions.pop(session_id)
|
||||
self.logger.info("cdm session closed")
|
||||
return 0
|
||||
else:
|
||||
self.logger.info("session {} not found".format(session_id))
|
||||
return 1
|
||||
|
||||
def set_service_certificate(self, session_id, cert_b64):
|
||||
self.logger.debug(
|
||||
"set_service_certificate(session_id={}, cert={})".format(
|
||||
session_id, cert_b64
|
||||
)
|
||||
)
|
||||
#self.logger.info("setting service certificate")
|
||||
|
||||
if session_id not in self.sessions:
|
||||
self.logger.error("session id doesn't exist")
|
||||
return 1
|
||||
|
||||
session = self.sessions[session_id]
|
||||
|
||||
message = wv_proto2.SignedMessage()
|
||||
|
||||
try:
|
||||
message.ParseFromString(base64.b64decode(cert_b64))
|
||||
except DecodeError:
|
||||
self.logger.error("failed to parse cert as SignedMessage")
|
||||
|
||||
service_certificate = wv_proto2.SignedDeviceCertificate()
|
||||
|
||||
if message.Type:
|
||||
self.logger.debug("service cert provided as signedmessage")
|
||||
try:
|
||||
service_certificate.ParseFromString(message.Msg)
|
||||
except DecodeError:
|
||||
# self.logger.error("failed to parse service certificate")
|
||||
return 1
|
||||
else:
|
||||
self.logger.debug("service cert provided as signeddevicecertificate")
|
||||
try:
|
||||
service_certificate.ParseFromString(base64.b64decode(cert_b64))
|
||||
except DecodeError:
|
||||
# self.logger.error("failed to parse service certificate")
|
||||
return 1
|
||||
|
||||
self.logger.debug("service certificate:")
|
||||
for line in text_format.MessageToString(service_certificate).splitlines():
|
||||
self.logger.debug(line)
|
||||
|
||||
session.service_certificate = service_certificate
|
||||
session.privacy_mode = True
|
||||
|
||||
return 0
|
||||
|
||||
def get_license_request(self, session_id):
|
||||
self.logger.debug("get_license_request(session_id={})".format(session_id))
|
||||
#self.logger.info("getting license request")
|
||||
|
||||
if session_id not in self.sessions:
|
||||
self.logger.error("session ID does not exist")
|
||||
return 1
|
||||
|
||||
session = self.sessions[session_id]
|
||||
|
||||
# raw pssh will be treated as bytes and not parsed
|
||||
if self.raw_pssh:
|
||||
license_request = wv_proto2.SignedLicenseRequestRaw()
|
||||
else:
|
||||
license_request = wv_proto2.SignedLicenseRequest()
|
||||
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:
|
||||
cid_bytes = client_id.ParseFromString(f.read())
|
||||
except DecodeError:
|
||||
self.logger.error("client id failed to parse as protobuf")
|
||||
return 1
|
||||
|
||||
self.logger.debug("building license request")
|
||||
if not self.raw_pssh:
|
||||
license_request.Type = wv_proto2.SignedLicenseRequest.MessageType.Value(
|
||||
"LICENSE_REQUEST"
|
||||
)
|
||||
license_request.Msg.ContentId.CencId.Pssh.CopyFrom(session.init_data)
|
||||
else:
|
||||
license_request.Type = wv_proto2.SignedLicenseRequestRaw.MessageType.Value(
|
||||
"LICENSE_REQUEST"
|
||||
)
|
||||
license_request.Msg.ContentId.CencId.Pssh = session.init_data # bytes
|
||||
|
||||
if session.offline:
|
||||
license_type = wv_proto2.LicenseType.Value("OFFLINE")
|
||||
else:
|
||||
license_type = wv_proto2.LicenseType.Value("DEFAULT")
|
||||
license_request.Msg.ContentId.CencId.LicenseType = license_type
|
||||
license_request.Msg.ContentId.CencId.RequestId = session_id
|
||||
license_request.Msg.Type = wv_proto2.LicenseRequest.RequestType.Value("NEW")
|
||||
license_request.Msg.RequestTime = int(time.time())
|
||||
license_request.Msg.ProtocolVersion = wv_proto2.ProtocolVersion.Value("CURRENT")
|
||||
if session.device_config.send_key_control_nonce:
|
||||
license_request.Msg.KeyControlNonce = random.randrange(1, 2 ** 31)
|
||||
|
||||
if session.privacy_mode:
|
||||
if session.device_config.vmp:
|
||||
self.logger.debug("vmp required, adding to client_id")
|
||||
self.logger.debug("reading vmp hashes")
|
||||
vmp_hashes = wv_proto2.FileHashes()
|
||||
with open(session.device_config.device_vmp_blob_filename, "rb") as f:
|
||||
try:
|
||||
vmp_bytes = vmp_hashes.ParseFromString(f.read())
|
||||
except DecodeError:
|
||||
self.logger.error("vmp hashes failed to parse as protobuf")
|
||||
return 1
|
||||
client_id._FileHashes.CopyFrom(vmp_hashes)
|
||||
self.logger.debug(
|
||||
"privacy mode & service certificate loaded, encrypting client id"
|
||||
)
|
||||
self.logger.debug("unencrypted client id:")
|
||||
for line in text_format.MessageToString(client_id).splitlines():
|
||||
self.logger.debug(line)
|
||||
cid_aes_key = get_random_bytes(16)
|
||||
cid_iv = get_random_bytes(16)
|
||||
|
||||
cid_cipher = AES.new(cid_aes_key, AES.MODE_CBC, cid_iv)
|
||||
|
||||
encrypted_client_id = cid_cipher.encrypt(
|
||||
Padding.pad(client_id.SerializeToString(), 16)
|
||||
)
|
||||
|
||||
service_public_key = RSA.importKey(
|
||||
session.service_certificate._DeviceCertificate.PublicKey
|
||||
)
|
||||
|
||||
service_cipher = PKCS1_OAEP.new(service_public_key)
|
||||
|
||||
encrypted_cid_key = service_cipher.encrypt(cid_aes_key)
|
||||
|
||||
encrypted_client_id_proto = wv_proto2.EncryptedClientIdentification()
|
||||
|
||||
encrypted_client_id_proto.ServiceId = (
|
||||
session.service_certificate._DeviceCertificate.ServiceId
|
||||
)
|
||||
encrypted_client_id_proto.ServiceCertificateSerialNumber = (
|
||||
session.service_certificate._DeviceCertificate.SerialNumber
|
||||
)
|
||||
encrypted_client_id_proto.EncryptedClientId = encrypted_client_id
|
||||
encrypted_client_id_proto.EncryptedClientIdIv = cid_iv
|
||||
encrypted_client_id_proto.EncryptedPrivacyKey = encrypted_cid_key
|
||||
|
||||
license_request.Msg.EncryptedClientId.CopyFrom(encrypted_client_id_proto)
|
||||
else:
|
||||
license_request.Msg.ClientId.CopyFrom(client_id)
|
||||
|
||||
if session.device_config.private_key_available:
|
||||
key = RSA.importKey(
|
||||
open(session.device_config.device_private_key_filename).read()
|
||||
)
|
||||
session.device_key = key
|
||||
else:
|
||||
self.logger.error("need device private key, other methods unimplemented")
|
||||
return 1
|
||||
|
||||
self.logger.debug("signing license request")
|
||||
|
||||
hash = SHA1.new(license_request.Msg.SerializeToString())
|
||||
signature = pss.new(key).sign(hash)
|
||||
|
||||
license_request.Signature = signature
|
||||
|
||||
session.license_request = license_request
|
||||
|
||||
self.logger.debug("license request:")
|
||||
for line in text_format.MessageToString(session.license_request).splitlines():
|
||||
self.logger.debug(line)
|
||||
#self.logger.info("license request created")
|
||||
self.logger.debug(
|
||||
"license request b64: {}".format(
|
||||
base64.b64encode(license_request.SerializeToString())
|
||||
)
|
||||
)
|
||||
return license_request.SerializeToString()
|
||||
|
||||
def provide_license(self, session_id, license_b64):
|
||||
self.logger.debug(
|
||||
"provide_license(session_id={}, license_b64={})".format(
|
||||
session_id, license_b64
|
||||
)
|
||||
)
|
||||
#self.logger.info("decrypting provided license")
|
||||
|
||||
if session_id not in self.sessions:
|
||||
self.logger.error("session does not exist")
|
||||
return 1
|
||||
|
||||
session = self.sessions[session_id]
|
||||
|
||||
if not session.license_request:
|
||||
self.logger.error("generate a license request first!")
|
||||
return 1
|
||||
|
||||
license = wv_proto2.SignedLicense()
|
||||
try:
|
||||
license.ParseFromString(base64.b64decode(license_b64))
|
||||
except DecodeError:
|
||||
self.logger.error("unable to parse license - check protobufs")
|
||||
return 1
|
||||
|
||||
session.license = license
|
||||
|
||||
self.logger.debug("license:")
|
||||
for line in text_format.MessageToString(license).splitlines():
|
||||
self.logger.debug(line)
|
||||
|
||||
self.logger.debug("deriving keys from session key")
|
||||
|
||||
oaep_cipher = PKCS1_OAEP.new(session.device_key)
|
||||
|
||||
session.session_key = oaep_cipher.decrypt(license.SessionKey)
|
||||
|
||||
lic_req_msg = session.license_request.Msg.SerializeToString()
|
||||
|
||||
enc_key_base = b"ENCRYPTION\000" + lic_req_msg + b"\0\0\0\x80"
|
||||
auth_key_base = b"AUTHENTICATION\0" + lic_req_msg + b"\0\0\2\0"
|
||||
|
||||
enc_key = b"\x01" + enc_key_base
|
||||
auth_key_1 = b"\x01" + auth_key_base
|
||||
auth_key_2 = b"\x02" + auth_key_base
|
||||
auth_key_3 = b"\x03" + auth_key_base
|
||||
auth_key_4 = b"\x04" + auth_key_base
|
||||
|
||||
cmac_obj = CMAC.new(session.session_key, ciphermod=AES)
|
||||
cmac_obj.update(enc_key)
|
||||
|
||||
enc_cmac_key = cmac_obj.digest()
|
||||
|
||||
cmac_obj = CMAC.new(session.session_key, ciphermod=AES)
|
||||
cmac_obj.update(auth_key_1)
|
||||
auth_cmac_key_1 = cmac_obj.digest()
|
||||
|
||||
cmac_obj = CMAC.new(session.session_key, ciphermod=AES)
|
||||
cmac_obj.update(auth_key_2)
|
||||
auth_cmac_key_2 = cmac_obj.digest()
|
||||
|
||||
cmac_obj = CMAC.new(session.session_key, ciphermod=AES)
|
||||
cmac_obj.update(auth_key_3)
|
||||
auth_cmac_key_3 = cmac_obj.digest()
|
||||
|
||||
cmac_obj = CMAC.new(session.session_key, ciphermod=AES)
|
||||
cmac_obj.update(auth_key_4)
|
||||
auth_cmac_key_4 = cmac_obj.digest()
|
||||
|
||||
auth_cmac_combined_1 = auth_cmac_key_1 + auth_cmac_key_2
|
||||
auth_cmac_combined_2 = auth_cmac_key_3 + auth_cmac_key_4
|
||||
|
||||
session.derived_keys["enc"] = enc_cmac_key
|
||||
session.derived_keys["auth_1"] = auth_cmac_combined_1
|
||||
session.derived_keys["auth_2"] = auth_cmac_combined_2
|
||||
|
||||
self.logger.debug("verifying license signature")
|
||||
|
||||
lic_hmac = HMAC.new(session.derived_keys["auth_1"], digestmod=SHA256)
|
||||
lic_hmac.update(license.Msg.SerializeToString())
|
||||
|
||||
self.logger.debug(
|
||||
"calculated sig: {} actual sig: {}".format(
|
||||
lic_hmac.hexdigest(), binascii.hexlify(license.Signature)
|
||||
)
|
||||
)
|
||||
|
||||
if lic_hmac.digest() != license.Signature:
|
||||
self.logger.info(
|
||||
"license signature doesn't match - writing bin so they can be debugged"
|
||||
)
|
||||
with open("original_lic.bin", "wb") as f:
|
||||
f.write(base64.b64decode(license_b64))
|
||||
with open("parsed_lic.bin", "wb") as f:
|
||||
f.write(license.SerializeToString())
|
||||
self.logger.info("continuing anyway")
|
||||
|
||||
self.logger.debug("key count: {}".format(len(license.Msg.Key)))
|
||||
for key in license.Msg.Key:
|
||||
if key.Id:
|
||||
key_id = key.Id
|
||||
else:
|
||||
key_id = wv_proto2.License.KeyContainer.KeyType.Name(key.Type).encode(
|
||||
"utf-8"
|
||||
)
|
||||
encrypted_key = key.Key
|
||||
iv = key.Iv
|
||||
type = wv_proto2.License.KeyContainer.KeyType.Name(key.Type)
|
||||
|
||||
cipher = AES.new(session.derived_keys["enc"], AES.MODE_CBC, iv=iv)
|
||||
decrypted_key = cipher.decrypt(encrypted_key)
|
||||
if type == "OPERATOR_SESSION":
|
||||
permissions = []
|
||||
perms = key._OperatorSessionKeyPermissions
|
||||
for (descriptor, value) in perms.ListFields():
|
||||
if value == 1:
|
||||
permissions.append(descriptor.name)
|
||||
# print(permissions)
|
||||
else:
|
||||
permissions = []
|
||||
session.keys.append(
|
||||
Key(key_id, type, Padding.unpad(decrypted_key, 16), permissions)
|
||||
)
|
||||
|
||||
#self.logger.info("decrypted all keys")
|
||||
return 0
|
||||
|
||||
def get_keys(self, session_id):
|
||||
if session_id in self.sessions:
|
||||
return self.sessions[session_id].keys
|
||||
else:
|
||||
self.logger.error("session not found")
|
||||
return 1
|
||||
115
pywidevine/cdm/deviceconfig.py
Normal file
115
pywidevine/cdm/deviceconfig.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import os
|
||||
|
||||
device_chromecdm_903 = {
|
||||
"name": "chromecdm_903",
|
||||
"description": "chrome cdm windows 903",
|
||||
"security_level": 3,
|
||||
"session_id_type": "chrome",
|
||||
"private_key_available": True,
|
||||
"vmp": False,
|
||||
"send_key_control_nonce": False,
|
||||
}
|
||||
|
||||
device_android_general = {
|
||||
"name": "android_general",
|
||||
"description": "android_general lvl3 security level",
|
||||
"security_level": 3,
|
||||
"session_id_type": "android",
|
||||
"private_key_available": True,
|
||||
"vmp": False,
|
||||
"send_key_control_nonce": True,
|
||||
}
|
||||
|
||||
devices_available = [
|
||||
device_android_general,
|
||||
device_chromecdm_903,
|
||||
]
|
||||
|
||||
FILES_FOLDER = "devices"
|
||||
|
||||
|
||||
class DeviceConfig:
|
||||
def __init__(self, device):
|
||||
self.device_name = device["name"]
|
||||
self.description = device["description"]
|
||||
self.security_level = device["security_level"]
|
||||
self.session_id_type = device["session_id_type"]
|
||||
self.private_key_available = device["private_key_available"]
|
||||
self.vmp = device["vmp"]
|
||||
self.send_key_control_nonce = device["send_key_control_nonce"]
|
||||
if "keybox_filename" in device:
|
||||
self.keybox_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
device["keybox_filename"],
|
||||
)
|
||||
else:
|
||||
self.keybox_filename = os.path.join(
|
||||
os.path.dirname(__file__), FILES_FOLDER, device["name"], "keybox"
|
||||
)
|
||||
if "device_cert_filename" in device:
|
||||
self.device_cert_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
device["device_cert_filename"],
|
||||
)
|
||||
else:
|
||||
self.device_cert_filename = os.path.join(
|
||||
os.path.dirname(__file__), FILES_FOLDER, device["name"], "device_cert"
|
||||
)
|
||||
if "device_private_key_filename" in device:
|
||||
self.device_private_key_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
device["device_private_key_filename"],
|
||||
)
|
||||
else:
|
||||
self.device_private_key_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
"device_private_key",
|
||||
)
|
||||
if "device_client_id_blob_filename" in device:
|
||||
self.device_client_id_blob_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
device["device_client_id_blob_filename"],
|
||||
)
|
||||
else:
|
||||
self.device_client_id_blob_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
"device_client_id_blob",
|
||||
)
|
||||
if "device_vmp_blob_filename" in device:
|
||||
self.device_vmp_blob_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
device["device_vmp_blob_filename"],
|
||||
)
|
||||
else:
|
||||
self.device_vmp_blob_filename = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
FILES_FOLDER,
|
||||
device["name"],
|
||||
"device_vmp_blob",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"DeviceConfig(name={}, description={}, security_level={}, session_id_type={}, private_key_available={}, vmp={})"
|
||||
).format(
|
||||
self.device_name,
|
||||
self.description,
|
||||
self.security_level,
|
||||
self.session_id_type,
|
||||
self.private_key_available,
|
||||
self.vmp,
|
||||
)
|
||||
BIN
pywidevine/cdm/devices/android_general/device_client_id_blob
Normal file
BIN
pywidevine/cdm/devices/android_general/device_client_id_blob
Normal file
Binary file not shown.
27
pywidevine/cdm/devices/android_general/device_private_key
Normal file
27
pywidevine/cdm/devices/android_general/device_private_key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA4sUKDpvMG/idF8oCH5AVSwFd5Mk+rEwOBsLZMYdliXWe1hn9
|
||||
mdE6u9pjsr+bLrZjlKxMFqPPxbIUcC1Ii7BFSje2Fd8kxnaIprQWxDPgK+NSSx7v
|
||||
Un452TyB1L9lx39ZBt0PlRfwjkCodX+I9y+oBga73NRh7hPbtLzXe/r/ubFBaEu+
|
||||
aRkDZBwYPqHgH1RoFLuyFNMjfqGcPosGxceDtvPysmBxB93Hk2evml5fjdYGg6tx
|
||||
z510g+XFPDFv7GSy1KuWqit83MqzPls9qAQMkwUc05ggjDhGCKW4/p97fn23WDFE
|
||||
3TzSSsQvyJLKA3s9oJbtJCD/gOHYqDvnWn8zPwIDAQABAoIBAQDCWe1Mp+o+7sx0
|
||||
XwWC15HoPruiIXg9YtGCqexLrqcvMEd5Z70Z32BfL8TSpbTyTA78lM6BeNPRs9Yg
|
||||
bi8GyYQZH7ZG+IAkN+LWPPJmJa+y7ZjSGSkzoksiC+GZ3I/2cwZyA3Qfa+0XfgLi
|
||||
8PMKJyXyREMt+DgWO57JQC/OakhRdCR19mM6NKd+ynd/IEz/NIbjMLDVKwW8HEPx
|
||||
N3r5CU9O96nr62DI68KVj3jwUR3cDi/5xfhosYhCQjHJuobNbeFR18dY2nQNLWYd
|
||||
S0wtskla1fl9eYHwYAzwru4wHT4WJC7+V4pscfCI0YZB6PslxDKrv73l5H1tz4cf
|
||||
Vy58NRSBAoGBAPSmjoVtQzTvQ6PZIs81SF1ulJI9kUpyFaBoSSgt+2ZkeNtF6Hih
|
||||
Zm7OVJ9wg9sfjpB3SFBUjuhXz/ts/t6dkA2PgCbrvhBMRKSGbfyhhtM2gRf002I4
|
||||
bJ7Y0C/ont4WzC/XbXEkAmh+fG2/JRvbdVQaIdyS6MmVHtCtRsHEQZS5AoGBAO1K
|
||||
IXOKAFA+320+Hkbqskfevmxrv+JHIdetliaREZwQH+VYUUM8u5/Kt3oyMat+mH90
|
||||
rZOKQK2zM8cz4tKclTUT54nrtICxeo6UHVc56FqXZ6sVvVgm8Cnvt1md4XwG4FwQ
|
||||
r/OlaM6Hr5HRf8dkzuzqm4ZQYRHGzZ6AMphj8Xu3AoGAdmo7p5dIJVH98kuCDrsi
|
||||
iJ6iaNpF/buUfiyb5EfFXD0bRj7jE6hDdTSHPxjtqVzv2zrxFHipJwqBz5dlEYlA
|
||||
FWA0ziHiv+66dsveZp4kLQ0/lMHaorre0E/vDJFSe/qa4DksbsvYIo2+WjxfkMk7
|
||||
U/bGFwZAiHmWDbkg+16rw3kCgYEAyyodWf9eJVavlakJ404vNrnP8KSQtfyRTUii
|
||||
toKewTBNHuBvM1JckoPOdCFlxZ+ukfIka56DojU8r+IM4qaOWdOg+sWE1mses9S9
|
||||
CmHaPzZC3IjQhRlRp5ZHNcOnu7lnf2wKOmH1Sl+CQydMcDwvr0lvv6AyfDXq9zps
|
||||
F2365CECgYEAmYgs/qwnh9m0aGDw/ZGrASoE0TxlpizPvsVDGx9t9UGC2Z+5QvAE
|
||||
ZcQeKoLCbktr0BnRLI+W1g+KpXQGcnSF9VX/qwUlf72XA6C6kobQvW+Yd/H/IN5d
|
||||
jPqoL/m41rRzm+J+9/Tfc8Aiy1kkllUYnVJdC5QLAIswuhI8lkaFTN4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
0
pywidevine/cdm/formats/__init__.py
Normal file
0
pywidevine/cdm/formats/__init__.py
Normal file
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-36.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-37.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-38.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-38.pyc
Normal file
Binary file not shown.
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-39.pyc
Normal file
BIN
pywidevine/cdm/formats/__pycache__/wv_proto2_pb2.cpython-39.pyc
Normal file
Binary file not shown.
466
pywidevine/cdm/formats/wv_proto2.proto
Normal file
466
pywidevine/cdm/formats/wv_proto2.proto
Normal file
@@ -0,0 +1,466 @@
|
||||
syntax = "proto2";
|
||||
|
||||
// from x86 (partial), most of it from the ARM version:
|
||||
message ClientIdentification {
|
||||
enum TokenType {
|
||||
KEYBOX = 0;
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
REMOTE_ATTESTATION_CERTIFICATE = 2;
|
||||
}
|
||||
message NameValue {
|
||||
required string Name = 1;
|
||||
required string Value = 2;
|
||||
}
|
||||
message ClientCapabilities {
|
||||
enum HdcpVersion {
|
||||
HDCP_NONE = 0;
|
||||
HDCP_V1 = 1;
|
||||
HDCP_V2 = 2;
|
||||
HDCP_V2_1 = 3;
|
||||
HDCP_V2_2 = 4;
|
||||
}
|
||||
optional uint32 ClientToken = 1;
|
||||
optional uint32 SessionToken = 2;
|
||||
optional uint32 VideoResolutionConstraints = 3;
|
||||
optional HdcpVersion MaxHdcpVersion = 4;
|
||||
optional uint32 OemCryptoApiVersion = 5;
|
||||
}
|
||||
required TokenType Type = 1;
|
||||
//optional bytes Token = 2; // by default the client treats this as blob, but it's usually a DeviceCertificate, so for usefulness sake, I'm replacing it with this one:
|
||||
optional SignedDeviceCertificate Token = 2; // use this when parsing, "bytes" when building a client id blob
|
||||
repeated NameValue ClientInfo = 3;
|
||||
optional bytes ProviderClientToken = 4;
|
||||
optional uint32 LicenseCounter = 5;
|
||||
optional ClientCapabilities _ClientCapabilities = 6; // how should we deal with duped names? will have to look at proto docs later
|
||||
optional FileHashes _FileHashes = 7; // vmp blob goes here
|
||||
}
|
||||
|
||||
message DeviceCertificate {
|
||||
enum CertificateType {
|
||||
ROOT = 0;
|
||||
INTERMEDIATE = 1;
|
||||
USER_DEVICE = 2;
|
||||
SERVICE = 3;
|
||||
}
|
||||
required CertificateType Type = 1; // the compiled code reused this as ProvisionedDeviceInfo.WvSecurityLevel, however that is incorrect (compiler aliased it as they're both identical as a structure)
|
||||
optional bytes SerialNumber = 2;
|
||||
optional uint32 CreationTimeSeconds = 3;
|
||||
optional bytes PublicKey = 4;
|
||||
optional uint32 SystemId = 5;
|
||||
optional uint32 TestDeviceDeprecated = 6; // is it bool or int?
|
||||
optional bytes ServiceId = 7; // service URL for service certificates
|
||||
}
|
||||
|
||||
// missing some references,
|
||||
message DeviceCertificateStatus {
|
||||
enum CertificateStatus {
|
||||
VALID = 0;
|
||||
REVOKED = 1;
|
||||
}
|
||||
optional bytes SerialNumber = 1;
|
||||
optional CertificateStatus Status = 2;
|
||||
optional ProvisionedDeviceInfo DeviceInfo = 4; // where is 3? is it deprecated?
|
||||
}
|
||||
|
||||
message DeviceCertificateStatusList {
|
||||
optional uint32 CreationTimeSeconds = 1;
|
||||
repeated DeviceCertificateStatus CertificateStatus = 2;
|
||||
}
|
||||
|
||||
message EncryptedClientIdentification {
|
||||
required string ServiceId = 1;
|
||||
optional bytes ServiceCertificateSerialNumber = 2;
|
||||
required bytes EncryptedClientId = 3;
|
||||
required bytes EncryptedClientIdIv = 4;
|
||||
required bytes EncryptedPrivacyKey = 5;
|
||||
}
|
||||
|
||||
// todo: fill (for this top-level type, it might be impossible/difficult)
|
||||
enum LicenseType {
|
||||
ZERO = 0;
|
||||
DEFAULT = 1; // 1 is STREAMING/temporary license; on recent versions may go up to 3 (latest x86); it might be persist/don't persist type, unconfirmed
|
||||
OFFLINE = 2;
|
||||
}
|
||||
|
||||
// todo: fill (for this top-level type, it might be impossible/difficult)
|
||||
// this is just a guess because these globals got lost, but really, do we need more?
|
||||
enum ProtocolVersion {
|
||||
CURRENT = 21; // don't have symbols for this
|
||||
}
|
||||
|
||||
|
||||
message LicenseIdentification {
|
||||
optional bytes RequestId = 1;
|
||||
optional bytes SessionId = 2;
|
||||
optional bytes PurchaseId = 3;
|
||||
optional LicenseType Type = 4;
|
||||
optional uint32 Version = 5;
|
||||
optional bytes ProviderSessionToken = 6;
|
||||
}
|
||||
|
||||
|
||||
message License {
|
||||
message Policy {
|
||||
optional bool CanPlay = 1; // changed from uint32 to bool
|
||||
optional bool CanPersist = 2;
|
||||
optional bool CanRenew = 3;
|
||||
optional uint32 RentalDurationSeconds = 4;
|
||||
optional uint32 PlaybackDurationSeconds = 5;
|
||||
optional uint32 LicenseDurationSeconds = 6;
|
||||
optional uint32 RenewalRecoveryDurationSeconds = 7;
|
||||
optional string RenewalServerUrl = 8;
|
||||
optional uint32 RenewalDelaySeconds = 9;
|
||||
optional uint32 RenewalRetryIntervalSeconds = 10;
|
||||
optional bool RenewWithUsage = 11; // was uint32
|
||||
}
|
||||
message KeyContainer {
|
||||
enum KeyType {
|
||||
SIGNING = 1;
|
||||
CONTENT = 2;
|
||||
KEY_CONTROL = 3;
|
||||
OPERATOR_SESSION = 4;
|
||||
}
|
||||
enum SecurityLevel {
|
||||
SW_SECURE_CRYPTO = 1;
|
||||
SW_SECURE_DECODE = 2;
|
||||
HW_SECURE_CRYPTO = 3;
|
||||
HW_SECURE_DECODE = 4;
|
||||
HW_SECURE_ALL = 5;
|
||||
}
|
||||
message OutputProtection {
|
||||
enum CGMS {
|
||||
COPY_FREE = 0;
|
||||
COPY_ONCE = 2;
|
||||
COPY_NEVER = 3;
|
||||
CGMS_NONE = 0x2A; // PC default!
|
||||
}
|
||||
optional ClientIdentification.ClientCapabilities.HdcpVersion Hdcp = 1; // it's most likely a copy of Hdcp version available here, but compiler optimized it away
|
||||
optional CGMS CgmsFlags = 2;
|
||||
}
|
||||
message KeyControl {
|
||||
required bytes KeyControlBlock = 1; // what is this?
|
||||
required bytes Iv = 2;
|
||||
}
|
||||
message OperatorSessionKeyPermissions {
|
||||
optional uint32 AllowEncrypt = 1;
|
||||
optional uint32 AllowDecrypt = 2;
|
||||
optional uint32 AllowSign = 3;
|
||||
optional uint32 AllowSignatureVerify = 4;
|
||||
}
|
||||
message VideoResolutionConstraint {
|
||||
optional uint32 MinResolutionPixels = 1;
|
||||
optional uint32 MaxResolutionPixels = 2;
|
||||
optional OutputProtection RequiredProtection = 3;
|
||||
}
|
||||
optional bytes Id = 1;
|
||||
optional bytes Iv = 2;
|
||||
optional bytes Key = 3;
|
||||
optional KeyType Type = 4;
|
||||
optional SecurityLevel Level = 5;
|
||||
optional OutputProtection RequiredProtection = 6;
|
||||
optional OutputProtection RequestedProtection = 7;
|
||||
optional KeyControl _KeyControl = 8; // duped names, etc
|
||||
optional OperatorSessionKeyPermissions _OperatorSessionKeyPermissions = 9; // duped names, etc
|
||||
repeated VideoResolutionConstraint VideoResolutionConstraints = 10;
|
||||
}
|
||||
optional LicenseIdentification Id = 1;
|
||||
optional Policy _Policy = 2; // duped names, etc
|
||||
repeated KeyContainer Key = 3;
|
||||
optional uint32 LicenseStartTime = 4;
|
||||
optional uint32 RemoteAttestationVerified = 5; // bool?
|
||||
optional bytes ProviderClientToken = 6;
|
||||
// there might be more, check with newer versions (I see field 7-8 in a lic)
|
||||
// this appeared in latest x86:
|
||||
optional uint32 ProtectionScheme = 7; // type unconfirmed fully, but it's likely as WidevineCencHeader describesit (fourcc)
|
||||
}
|
||||
|
||||
message LicenseError {
|
||||
enum Error {
|
||||
INVALID_DEVICE_CERTIFICATE = 1;
|
||||
REVOKED_DEVICE_CERTIFICATE = 2;
|
||||
SERVICE_UNAVAILABLE = 3;
|
||||
}
|
||||
//LicenseRequest.RequestType ErrorCode; // clang mismatch
|
||||
optional Error ErrorCode = 1;
|
||||
}
|
||||
|
||||
message LicenseRequest {
|
||||
message ContentIdentification {
|
||||
message CENC {
|
||||
//optional bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with:
|
||||
optional WidevineCencHeader Pssh = 1;
|
||||
optional LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1 (is this persist/don't persist? look into it!)
|
||||
optional bytes RequestId = 3;
|
||||
}
|
||||
message WebM {
|
||||
optional bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used
|
||||
optional LicenseType LicenseType = 2;
|
||||
optional bytes RequestId = 3;
|
||||
}
|
||||
message ExistingLicense {
|
||||
optional LicenseIdentification LicenseId = 1;
|
||||
optional uint32 SecondsSinceStarted = 2;
|
||||
optional uint32 SecondsSinceLastPlayed = 3;
|
||||
optional bytes SessionUsageTableEntry = 4; // interesting! try to figure out the connection between the usage table blob and KCB!
|
||||
}
|
||||
optional CENC CencId = 1;
|
||||
optional WebM WebmId = 2;
|
||||
optional ExistingLicense License = 3;
|
||||
}
|
||||
enum RequestType {
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
RELEASE = 3;
|
||||
}
|
||||
optional ClientIdentification ClientId = 1;
|
||||
optional ContentIdentification ContentId = 2;
|
||||
optional RequestType Type = 3;
|
||||
optional uint32 RequestTime = 4;
|
||||
optional bytes KeyControlNonceDeprecated = 5;
|
||||
optional ProtocolVersion ProtocolVersion = 6; // lacking symbols for this
|
||||
optional uint32 KeyControlNonce = 7;
|
||||
optional EncryptedClientIdentification EncryptedClientId = 8;
|
||||
}
|
||||
|
||||
// raw pssh hack
|
||||
message LicenseRequestRaw {
|
||||
message ContentIdentification {
|
||||
message CENC {
|
||||
optional bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with:
|
||||
//optional WidevineCencHeader Pssh = 1;
|
||||
optional LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1 (is this persist/don't persist? look into it!)
|
||||
optional bytes RequestId = 3;
|
||||
}
|
||||
message WebM {
|
||||
optional bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used
|
||||
optional LicenseType LicenseType = 2;
|
||||
optional bytes RequestId = 3;
|
||||
}
|
||||
message ExistingLicense {
|
||||
optional LicenseIdentification LicenseId = 1;
|
||||
optional uint32 SecondsSinceStarted = 2;
|
||||
optional uint32 SecondsSinceLastPlayed = 3;
|
||||
optional bytes SessionUsageTableEntry = 4; // interesting! try to figure out the connection between the usage table blob and KCB!
|
||||
}
|
||||
optional CENC CencId = 1;
|
||||
optional WebM WebmId = 2;
|
||||
optional ExistingLicense License = 3;
|
||||
}
|
||||
enum RequestType {
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
RELEASE = 3;
|
||||
}
|
||||
optional ClientIdentification ClientId = 1;
|
||||
optional ContentIdentification ContentId = 2;
|
||||
optional RequestType Type = 3;
|
||||
optional uint32 RequestTime = 4;
|
||||
optional bytes KeyControlNonceDeprecated = 5;
|
||||
optional ProtocolVersion ProtocolVersion = 6; // lacking symbols for this
|
||||
optional uint32 KeyControlNonce = 7;
|
||||
optional EncryptedClientIdentification EncryptedClientId = 8;
|
||||
}
|
||||
|
||||
|
||||
message ProvisionedDeviceInfo {
|
||||
enum WvSecurityLevel {
|
||||
LEVEL_UNSPECIFIED = 0;
|
||||
LEVEL_1 = 1;
|
||||
LEVEL_2 = 2;
|
||||
LEVEL_3 = 3;
|
||||
}
|
||||
optional uint32 SystemId = 1;
|
||||
optional string Soc = 2;
|
||||
optional string Manufacturer = 3;
|
||||
optional string Model = 4;
|
||||
optional string DeviceType = 5;
|
||||
optional uint32 ModelYear = 6;
|
||||
optional WvSecurityLevel SecurityLevel = 7;
|
||||
optional uint32 TestDevice = 8; // bool?
|
||||
}
|
||||
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningOptions {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningRequest {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningResponse {
|
||||
}
|
||||
|
||||
message RemoteAttestation {
|
||||
optional EncryptedClientIdentification Certificate = 1;
|
||||
optional string Salt = 2;
|
||||
optional string Signature = 3;
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SessionInit {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SessionState {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SignedCertificateStatusList {
|
||||
}
|
||||
|
||||
message SignedDeviceCertificate {
|
||||
|
||||
//optional bytes DeviceCertificate = 1; // again, they use a buffer where it's supposed to be a message, so we'll replace it with what it really is:
|
||||
optional DeviceCertificate _DeviceCertificate = 1; // how should we deal with duped names? will have to look at proto docs later
|
||||
optional bytes Signature = 2;
|
||||
optional SignedDeviceCertificate Signer = 3;
|
||||
}
|
||||
|
||||
|
||||
// todo: fill
|
||||
message SignedProvisioningMessage {
|
||||
}
|
||||
|
||||
// the root of all messages, from either server or client
|
||||
message SignedMessage {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
optional bytes Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
optional bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
optional RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This message is copied from google's docs, not reversed:
|
||||
message WidevineCencHeader {
|
||||
enum Algorithm {
|
||||
UNENCRYPTED = 0;
|
||||
AESCTR = 1;
|
||||
};
|
||||
optional Algorithm algorithm = 1;
|
||||
repeated bytes key_id = 2;
|
||||
|
||||
// Content provider name.
|
||||
optional string provider = 3;
|
||||
|
||||
// A content identifier, specified by content provider.
|
||||
optional bytes content_id = 4;
|
||||
|
||||
// Track type. Acceptable values are SD, HD and AUDIO. Used to
|
||||
// differentiate content keys used by an asset.
|
||||
optional string track_type_deprecated = 5;
|
||||
|
||||
// The name of a registered policy to be used for this asset.
|
||||
optional string policy = 6;
|
||||
|
||||
// Crypto period index, for media using key rotation.
|
||||
optional uint32 crypto_period_index = 7;
|
||||
|
||||
// Optional protected context for group content. The grouped_license is a
|
||||
// serialized SignedMessage.
|
||||
optional bytes grouped_license = 8;
|
||||
|
||||
// Protection scheme identifying the encryption algorithm.
|
||||
// Represented as one of the following 4CC values:
|
||||
// 'cenc' (AESCTR), 'cbc1' (AESCBC),
|
||||
// 'cens' (AESCTR subsample), 'cbcs' (AESCBC subsample).
|
||||
optional uint32 protection_scheme = 9;
|
||||
|
||||
// Optional. For media using key rotation, this represents the duration
|
||||
// of each crypto period in seconds.
|
||||
optional uint32 crypto_period_seconds = 10;
|
||||
}
|
||||
|
||||
|
||||
// remove these when using it outside of protoc:
|
||||
|
||||
// from here on, it's just for testing, these messages don't exist in the binaries, I'm adding them to avoid detecting type programmatically
|
||||
message SignedLicenseRequest {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
optional LicenseRequest Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
optional bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
optional RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
// hack
|
||||
message SignedLicenseRequestRaw {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
optional LicenseRequestRaw Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
optional bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
optional RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
|
||||
message SignedLicense {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
optional License Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
optional bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
optional RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
message SignedServiceCertificate {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
optional SignedDeviceCertificate Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
optional bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
optional RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
//vmp support
|
||||
message FileHashes {
|
||||
message Signature {
|
||||
optional string filename = 1;
|
||||
optional bool test_signing = 2; //0 - release, 1 - testing
|
||||
optional bytes SHA512Hash = 3;
|
||||
optional bool main_exe = 4; //0 for dlls, 1 for exe, this is field 3 in file
|
||||
optional bytes signature = 5;
|
||||
}
|
||||
optional bytes signer = 1;
|
||||
repeated Signature signatures = 2;
|
||||
}
|
||||
3324
pywidevine/cdm/formats/wv_proto2_pb2.py
Normal file
3324
pywidevine/cdm/formats/wv_proto2_pb2.py
Normal file
File diff suppressed because one or more lines are too long
389
pywidevine/cdm/formats/wv_proto3.proto
Normal file
389
pywidevine/cdm/formats/wv_proto3.proto
Normal file
@@ -0,0 +1,389 @@
|
||||
// beware proto3 won't show missing fields it seems, need to change to "proto2" and add "optional" before every field, and remove all the dummy enum members I added:
|
||||
syntax = "proto3";
|
||||
|
||||
// from x86 (partial), most of it from the ARM version:
|
||||
message ClientIdentification {
|
||||
enum TokenType {
|
||||
KEYBOX = 0;
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
REMOTE_ATTESTATION_CERTIFICATE = 2;
|
||||
}
|
||||
message NameValue {
|
||||
string Name = 1;
|
||||
string Value = 2;
|
||||
}
|
||||
message ClientCapabilities {
|
||||
enum HdcpVersion {
|
||||
HDCP_NONE = 0;
|
||||
HDCP_V1 = 1;
|
||||
HDCP_V2 = 2;
|
||||
HDCP_V2_1 = 3;
|
||||
HDCP_V2_2 = 4;
|
||||
}
|
||||
uint32 ClientToken = 1;
|
||||
uint32 SessionToken = 2;
|
||||
uint32 VideoResolutionConstraints = 3;
|
||||
HdcpVersion MaxHdcpVersion = 4;
|
||||
uint32 OemCryptoApiVersion = 5;
|
||||
}
|
||||
TokenType Type = 1;
|
||||
//bytes Token = 2; // by default the client treats this as blob, but it's usually a DeviceCertificate, so for usefulness sake, I'm replacing it with this one:
|
||||
SignedDeviceCertificate Token = 2;
|
||||
repeated NameValue ClientInfo = 3;
|
||||
bytes ProviderClientToken = 4;
|
||||
uint32 LicenseCounter = 5;
|
||||
ClientCapabilities _ClientCapabilities = 6; // how should we deal with duped names? will have to look at proto docs later
|
||||
}
|
||||
|
||||
message DeviceCertificate {
|
||||
enum CertificateType {
|
||||
ROOT = 0;
|
||||
INTERMEDIATE = 1;
|
||||
USER_DEVICE = 2;
|
||||
SERVICE = 3;
|
||||
}
|
||||
//ProvisionedDeviceInfo.WvSecurityLevel Type = 1; // is this how one is supposed to call it? (it's an enum) there might be a bug here, with CertificateType getting confused with WvSecurityLevel, for now renaming it (verify against other binaries)
|
||||
CertificateType Type = 1;
|
||||
bytes SerialNumber = 2;
|
||||
uint32 CreationTimeSeconds = 3;
|
||||
bytes PublicKey = 4;
|
||||
uint32 SystemId = 5;
|
||||
uint32 TestDeviceDeprecated = 6; // is it bool or int?
|
||||
bytes ServiceId = 7; // service URL for service certificates
|
||||
}
|
||||
|
||||
// missing some references,
|
||||
message DeviceCertificateStatus {
|
||||
enum CertificateStatus {
|
||||
VALID = 0;
|
||||
REVOKED = 1;
|
||||
}
|
||||
bytes SerialNumber = 1;
|
||||
CertificateStatus Status = 2;
|
||||
ProvisionedDeviceInfo DeviceInfo = 4; // where is 3? is it deprecated?
|
||||
}
|
||||
|
||||
message DeviceCertificateStatusList {
|
||||
uint32 CreationTimeSeconds = 1;
|
||||
repeated DeviceCertificateStatus CertificateStatus = 2;
|
||||
}
|
||||
|
||||
message EncryptedClientIdentification {
|
||||
string ServiceId = 1;
|
||||
bytes ServiceCertificateSerialNumber = 2;
|
||||
bytes EncryptedClientId = 3;
|
||||
bytes EncryptedClientIdIv = 4;
|
||||
bytes EncryptedPrivacyKey = 5;
|
||||
}
|
||||
|
||||
// todo: fill (for this top-level type, it might be impossible/difficult)
|
||||
enum LicenseType {
|
||||
ZERO = 0;
|
||||
DEFAULT = 1; // do not know what this is either, but should be 1; on recent versions may go up to 3 (latest x86)
|
||||
}
|
||||
|
||||
// todo: fill (for this top-level type, it might be impossible/difficult)
|
||||
// this is just a guess because these globals got lost, but really, do we need more?
|
||||
enum ProtocolVersion {
|
||||
DUMMY = 0;
|
||||
CURRENT = 21; // don't have symbols for this
|
||||
}
|
||||
|
||||
|
||||
message LicenseIdentification {
|
||||
bytes RequestId = 1;
|
||||
bytes SessionId = 2;
|
||||
bytes PurchaseId = 3;
|
||||
LicenseType Type = 4;
|
||||
uint32 Version = 5;
|
||||
bytes ProviderSessionToken = 6;
|
||||
}
|
||||
|
||||
|
||||
message License {
|
||||
message Policy {
|
||||
uint32 CanPlay = 1;
|
||||
uint32 CanPersist = 2;
|
||||
uint32 CanRenew = 3;
|
||||
uint32 RentalDurationSeconds = 4;
|
||||
uint32 PlaybackDurationSeconds = 5;
|
||||
uint32 LicenseDurationSeconds = 6;
|
||||
uint32 RenewalRecoveryDurationSeconds = 7;
|
||||
string RenewalServerUrl = 8;
|
||||
uint32 RenewalDelaySeconds = 9;
|
||||
uint32 RenewalRetryIntervalSeconds = 10;
|
||||
uint32 RenewWithUsage = 11;
|
||||
uint32 UnknownPolicy12 = 12;
|
||||
}
|
||||
message KeyContainer {
|
||||
enum KeyType {
|
||||
_NOKEYTYPE = 0; // dummy, added to satisfy proto3, not present in original
|
||||
SIGNING = 1;
|
||||
CONTENT = 2;
|
||||
KEY_CONTROL = 3;
|
||||
OPERATOR_SESSION = 4;
|
||||
}
|
||||
enum SecurityLevel {
|
||||
_NOSECLEVEL = 0; // dummy, added to satisfy proto3, not present in original
|
||||
SW_SECURE_CRYPTO = 1;
|
||||
SW_SECURE_DECODE = 2;
|
||||
HW_SECURE_CRYPTO = 3;
|
||||
HW_SECURE_DECODE = 4;
|
||||
HW_SECURE_ALL = 5;
|
||||
}
|
||||
message OutputProtection {
|
||||
enum CGMS {
|
||||
COPY_FREE = 0;
|
||||
COPY_ONCE = 2;
|
||||
COPY_NEVER = 3;
|
||||
CGMS_NONE = 0x2A; // PC default!
|
||||
}
|
||||
ClientIdentification.ClientCapabilities.HdcpVersion Hdcp = 1; // it's most likely a copy of Hdcp version available here, but compiler optimized it away
|
||||
CGMS CgmsFlags = 2;
|
||||
}
|
||||
message KeyControl {
|
||||
bytes KeyControlBlock = 1; // what is this?
|
||||
bytes Iv = 2;
|
||||
}
|
||||
message OperatorSessionKeyPermissions {
|
||||
uint32 AllowEncrypt = 1;
|
||||
uint32 AllowDecrypt = 2;
|
||||
uint32 AllowSign = 3;
|
||||
uint32 AllowSignatureVerify = 4;
|
||||
}
|
||||
message VideoResolutionConstraint {
|
||||
uint32 MinResolutionPixels = 1;
|
||||
uint32 MaxResolutionPixels = 2;
|
||||
OutputProtection RequiredProtection = 3;
|
||||
}
|
||||
bytes Id = 1;
|
||||
bytes Iv = 2;
|
||||
bytes Key = 3;
|
||||
KeyType Type = 4;
|
||||
SecurityLevel Level = 5;
|
||||
OutputProtection RequiredProtection = 6;
|
||||
OutputProtection RequestedProtection = 7;
|
||||
KeyControl _KeyControl = 8; // duped names, etc
|
||||
OperatorSessionKeyPermissions _OperatorSessionKeyPermissions = 9; // duped names, etc
|
||||
repeated VideoResolutionConstraint VideoResolutionConstraints = 10;
|
||||
}
|
||||
LicenseIdentification Id = 1;
|
||||
Policy _Policy = 2; // duped names, etc
|
||||
repeated KeyContainer Key = 3;
|
||||
uint32 LicenseStartTime = 4;
|
||||
uint32 RemoteAttestationVerified = 5; // bool?
|
||||
bytes ProviderClientToken = 6;
|
||||
// there might be more, check with newer versions (I see field 7-8 in a lic)
|
||||
// this appeared in latest x86:
|
||||
uint32 ProtectionScheme = 7; // type unconfirmed fully, but it's likely as WidevineCencHeader describesit (fourcc)
|
||||
bytes UnknownHdcpDataField = 8;
|
||||
}
|
||||
|
||||
message LicenseError {
|
||||
enum Error {
|
||||
DUMMY_NO_ERROR = 0; // dummy, added to satisfy proto3
|
||||
INVALID_DEVICE_CERTIFICATE = 1;
|
||||
REVOKED_DEVICE_CERTIFICATE = 2;
|
||||
SERVICE_UNAVAILABLE = 3;
|
||||
}
|
||||
//LicenseRequest.RequestType ErrorCode; // clang mismatch
|
||||
Error ErrorCode = 1;
|
||||
}
|
||||
|
||||
message LicenseRequest {
|
||||
message ContentIdentification {
|
||||
message CENC {
|
||||
// bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with:
|
||||
WidevineCencHeader Pssh = 1;
|
||||
LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1
|
||||
bytes RequestId = 3;
|
||||
}
|
||||
message WebM {
|
||||
bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used
|
||||
LicenseType LicenseType = 2;
|
||||
bytes RequestId = 3;
|
||||
}
|
||||
message ExistingLicense {
|
||||
LicenseIdentification LicenseId = 1;
|
||||
uint32 SecondsSinceStarted = 2;
|
||||
uint32 SecondsSinceLastPlayed = 3;
|
||||
bytes SessionUsageTableEntry = 4;
|
||||
}
|
||||
CENC CencId = 1;
|
||||
WebM WebmId = 2;
|
||||
ExistingLicense License = 3;
|
||||
}
|
||||
enum RequestType {
|
||||
DUMMY_REQ_TYPE = 0; // dummy, added to satisfy proto3
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
RELEASE = 3;
|
||||
}
|
||||
ClientIdentification ClientId = 1;
|
||||
ContentIdentification ContentId = 2;
|
||||
RequestType Type = 3;
|
||||
uint32 RequestTime = 4;
|
||||
bytes KeyControlNonceDeprecated = 5;
|
||||
ProtocolVersion ProtocolVersion = 6; // lacking symbols for this
|
||||
uint32 KeyControlNonce = 7;
|
||||
EncryptedClientIdentification EncryptedClientId = 8;
|
||||
}
|
||||
|
||||
message ProvisionedDeviceInfo {
|
||||
enum WvSecurityLevel {
|
||||
LEVEL_UNSPECIFIED = 0;
|
||||
LEVEL_1 = 1;
|
||||
LEVEL_2 = 2;
|
||||
LEVEL_3 = 3;
|
||||
}
|
||||
uint32 SystemId = 1;
|
||||
string Soc = 2;
|
||||
string Manufacturer = 3;
|
||||
string Model = 4;
|
||||
string DeviceType = 5;
|
||||
uint32 ModelYear = 6;
|
||||
WvSecurityLevel SecurityLevel = 7;
|
||||
uint32 TestDevice = 8; // bool?
|
||||
}
|
||||
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningOptions {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningRequest {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message ProvisioningResponse {
|
||||
}
|
||||
|
||||
message RemoteAttestation {
|
||||
EncryptedClientIdentification Certificate = 1;
|
||||
string Salt = 2;
|
||||
string Signature = 3;
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SessionInit {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SessionState {
|
||||
}
|
||||
|
||||
// todo: fill
|
||||
message SignedCertificateStatusList {
|
||||
}
|
||||
|
||||
message SignedDeviceCertificate {
|
||||
|
||||
//bytes DeviceCertificate = 1; // again, they use a buffer where it's supposed to be a message, so we'll replace it with what it really is:
|
||||
DeviceCertificate _DeviceCertificate = 1; // how should we deal with duped names? will have to look at proto docs later
|
||||
bytes Signature = 2;
|
||||
SignedDeviceCertificate Signer = 3;
|
||||
}
|
||||
|
||||
|
||||
// todo: fill
|
||||
message SignedProvisioningMessage {
|
||||
}
|
||||
|
||||
// the root of all messages, from either server or client
|
||||
message SignedMessage {
|
||||
enum MessageType {
|
||||
DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
bytes Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// This message is copied from google's docs, not reversed:
|
||||
message WidevineCencHeader {
|
||||
enum Algorithm {
|
||||
UNENCRYPTED = 0;
|
||||
AESCTR = 1;
|
||||
};
|
||||
Algorithm algorithm = 1;
|
||||
repeated bytes key_id = 2;
|
||||
|
||||
// Content provider name.
|
||||
string provider = 3;
|
||||
|
||||
// A content identifier, specified by content provider.
|
||||
bytes content_id = 4;
|
||||
|
||||
// Track type. Acceptable values are SD, HD and AUDIO. Used to
|
||||
// differentiate content keys used by an asset.
|
||||
string track_type_deprecated = 5;
|
||||
|
||||
// The name of a registered policy to be used for this asset.
|
||||
string policy = 6;
|
||||
|
||||
// Crypto period index, for media using key rotation.
|
||||
uint32 crypto_period_index = 7;
|
||||
|
||||
// Optional protected context for group content. The grouped_license is a
|
||||
// serialized SignedMessage.
|
||||
bytes grouped_license = 8;
|
||||
|
||||
// Protection scheme identifying the encryption algorithm.
|
||||
// Represented as one of the following 4CC values:
|
||||
// 'cenc' (AESCTR), 'cbc1' (AESCBC),
|
||||
// 'cens' (AESCTR subsample), 'cbcs' (AESCBC subsample).
|
||||
uint32 protection_scheme = 9;
|
||||
|
||||
// Optional. For media using key rotation, this represents the duration
|
||||
// of each crypto period in seconds.
|
||||
uint32 crypto_period_seconds = 10;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// from here on, it's just for testing, these messages don't exist in the binaries, I'm adding them to avoid detecting type programmatically
|
||||
message SignedLicenseRequest {
|
||||
enum MessageType {
|
||||
DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
LicenseRequest Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
|
||||
message SignedLicense {
|
||||
enum MessageType {
|
||||
DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel
|
||||
License Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present
|
||||
// for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE
|
||||
bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now)
|
||||
bytes SessionKey = 4; // often RSA wrapped for licenses
|
||||
RemoteAttestation RemoteAttestation = 5;
|
||||
}
|
||||
2686
pywidevine/cdm/formats/wv_proto3_pb2.py
Normal file
2686
pywidevine/cdm/formats/wv_proto3_pb2.py
Normal file
File diff suppressed because one or more lines are too long
19
pywidevine/cdm/key.py
Normal file
19
pywidevine/cdm/key.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# uncompyle6 version 3.3.2
|
||||
# Python bytecode 3.6 (3379)
|
||||
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
|
||||
# Embedded file name: pywidevine\cdm\key.py
|
||||
import binascii
|
||||
|
||||
class Key:
|
||||
|
||||
def __init__(self, kid, type, key, permissions=[]):
|
||||
self.kid = kid
|
||||
self.type = type
|
||||
self.key = key
|
||||
self.permissions = permissions
|
||||
|
||||
def __repr__(self):
|
||||
if self.type == 'OPERATOR_SESSION':
|
||||
return ('key(kid={}, type={}, key={}, permissions={})').format(self.kid, self.type, binascii.hexlify(self.key), self.permissions)
|
||||
else:
|
||||
return ('key(kid={}, type={}, key={})').format(self.kid, self.type, binascii.hexlify(self.key))
|
||||
23
pywidevine/cdm/session.py
Normal file
23
pywidevine/cdm/session.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# uncompyle6 version 3.3.2
|
||||
# Python bytecode 3.6 (3379)
|
||||
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
|
||||
# Embedded file name: pywidevine\cdm\session.py
|
||||
|
||||
|
||||
class Session:
|
||||
|
||||
def __init__(self, session_id, init_data, device_config, offline):
|
||||
self.session_id = session_id
|
||||
self.init_data = init_data
|
||||
self.offline = offline
|
||||
self.device_config = device_config
|
||||
self.device_key = None
|
||||
self.session_key = None
|
||||
self.derived_keys = {'enc':None,
|
||||
'auth_1':None,
|
||||
'auth_2':None}
|
||||
self.license_request = None
|
||||
self.license = None
|
||||
self.service_certificate = None
|
||||
self.privacy_mode = False
|
||||
self.keys = []
|
||||
Reference in New Issue
Block a user