Cdm: Support providing Service Cert as any 3 schemas
Some service's might provide the Service Certificate as a SignedDrmCertificate instead of a SignedMessage so I added support for supplying such format certificates. I also added support for supplying a DrmCertificate directly, though it's unlikely for a service to provide it raw without a signature like that. The Service Certificate is now also stored as just the DrmCertificate internally, as it will not be using the signature.
This commit is contained in:
parent
70e79825b3
commit
95982725c3
|
@ -84,10 +84,10 @@ class Cdm:
|
||||||
self.init_data = PSSH.get_as_box(pssh).init_data
|
self.init_data = PSSH.get_as_box(pssh).init_data
|
||||||
|
|
||||||
self.session_id = get_random_bytes(16)
|
self.session_id = get_random_bytes(16)
|
||||||
self.service_certificate: Optional[SignedMessage] = None
|
self.service_certificate: Optional[DrmCertificate] = None
|
||||||
self.context: dict[bytes, tuple[bytes, bytes]] = {}
|
self.context: dict[bytes, tuple[bytes, bytes]] = {}
|
||||||
|
|
||||||
def set_service_certificate(self, certificate: Union[bytes, str]) -> SignedMessage:
|
def set_service_certificate(self, certificate: Union[bytes, str]) -> DrmCertificate:
|
||||||
"""
|
"""
|
||||||
Set a Service Privacy Certificate for Privacy Mode. (optional but recommended)
|
Set a Service Privacy Certificate for Privacy Mode. (optional but recommended)
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ class Cdm:
|
||||||
Some services have their own, but most use the common privacy cert,
|
Some services have their own, but most use the common privacy cert,
|
||||||
(common_privacy_cert).
|
(common_privacy_cert).
|
||||||
|
|
||||||
Returns the parsed Signed Message if successful, otherwise raises a DecodeError.
|
Returns the parsed Drm Certificate if successful, otherwise raises a DecodeError.
|
||||||
|
|
||||||
The Service Certificate is used to encrypt Client IDs in Licenses. This is also
|
The Service Certificate is used to encrypt Client IDs in Licenses. This is also
|
||||||
known as Privacy Mode and may be required for some services or for some devices.
|
known as Privacy Mode and may be required for some services or for some devices.
|
||||||
|
@ -106,13 +106,48 @@ class Cdm:
|
||||||
certificate = base64.b64decode(certificate) # assuming base64
|
certificate = base64.b64decode(certificate) # assuming base64
|
||||||
|
|
||||||
signed_message = SignedMessage()
|
signed_message = SignedMessage()
|
||||||
try:
|
signed_drm_certificate = SignedDrmCertificate()
|
||||||
signed_message.ParseFromString(certificate)
|
drm_certificate = DrmCertificate()
|
||||||
except DecodeError as e:
|
|
||||||
raise DecodeError(f"Could not parse certificate as a Signed Message: {e}")
|
|
||||||
|
|
||||||
self.service_certificate = signed_message
|
# Note: A secure CDM would likely reject any Service Certificate that is
|
||||||
return signed_message
|
# not either a SignedMessage or a SignedDrmCertificate. This is because
|
||||||
|
# the DrmCertificate itself is not signed. This CDM does not verify the
|
||||||
|
# signatures as I'm not sure what HMAC key is used. At this stage of the
|
||||||
|
# CDM flow, we wouldn't have any mac_keys, and those might not work for
|
||||||
|
# verifying service certificate signatures (likely not).
|
||||||
|
|
||||||
|
# All these 3 schemas can sort of parse each other in a minimal buggy way,
|
||||||
|
# so we have to parse down the full chain instead of relaying each step
|
||||||
|
|
||||||
|
try: # SignedMessage input
|
||||||
|
signed_message.ParseFromString(certificate)
|
||||||
|
signed_drm_certificate.ParseFromString(signed_message.msg)
|
||||||
|
if not signed_drm_certificate.drm_certificate:
|
||||||
|
raise DecodeError()
|
||||||
|
drm_certificate.ParseFromString(signed_drm_certificate.drm_certificate)
|
||||||
|
self.service_certificate = drm_certificate
|
||||||
|
return self.service_certificate
|
||||||
|
except DecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # SignedDrmCertificate input
|
||||||
|
signed_drm_certificate.ParseFromString(certificate)
|
||||||
|
if not signed_drm_certificate.drm_certificate:
|
||||||
|
raise DecodeError()
|
||||||
|
drm_certificate.ParseFromString(signed_drm_certificate.drm_certificate)
|
||||||
|
self.service_certificate = drm_certificate
|
||||||
|
return self.service_certificate
|
||||||
|
except DecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # DrmCertificate input
|
||||||
|
drm_certificate.ParseFromString(certificate)
|
||||||
|
self.service_certificate = drm_certificate
|
||||||
|
return self.service_certificate
|
||||||
|
except DecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise DecodeError("Could not parse certificate as a Service Certificate")
|
||||||
|
|
||||||
def get_license_challenge(self, type_: LicenseType = LicenseType.STREAMING, privacy_mode: bool = True) -> bytes:
|
def get_license_challenge(self, type_: LicenseType = LicenseType.STREAMING, privacy_mode: bool = True) -> bytes:
|
||||||
"""
|
"""
|
||||||
|
@ -253,7 +288,7 @@ class Cdm:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encrypt_client_id(
|
def encrypt_client_id(
|
||||||
client_id: ClientIdentification,
|
client_id: ClientIdentification,
|
||||||
service_certificate: Union[SignedMessage, SignedDrmCertificate, DrmCertificate],
|
service_certificate: DrmCertificate,
|
||||||
key: bytes = None,
|
key: bytes = None,
|
||||||
iv: bytes = None
|
iv: bytes = None
|
||||||
) -> EncryptedClientIdentification:
|
) -> EncryptedClientIdentification:
|
||||||
|
@ -261,18 +296,8 @@ class Cdm:
|
||||||
privacy_key = key or get_random_bytes(16)
|
privacy_key = key or get_random_bytes(16)
|
||||||
privacy_iv = iv or get_random_bytes(16)
|
privacy_iv = iv or get_random_bytes(16)
|
||||||
|
|
||||||
if isinstance(service_certificate, SignedMessage):
|
|
||||||
signed_service_certificate = SignedDrmCertificate()
|
|
||||||
signed_service_certificate.ParseFromString(service_certificate.msg)
|
|
||||||
service_certificate = signed_service_certificate
|
|
||||||
|
|
||||||
if isinstance(service_certificate, SignedDrmCertificate):
|
|
||||||
service_service_drm_certificate = DrmCertificate()
|
|
||||||
service_service_drm_certificate.ParseFromString(service_certificate.drm_certificate)
|
|
||||||
service_certificate = service_service_drm_certificate
|
|
||||||
|
|
||||||
if not isinstance(service_certificate, DrmCertificate):
|
if not isinstance(service_certificate, DrmCertificate):
|
||||||
raise ValueError(f"Service Certificate is in an unexpected type {service_certificate!r}")
|
raise ValueError(f"Expecting Service Certificate to be a DrmCertificate, not {service_certificate!r}")
|
||||||
|
|
||||||
enc_client_id = EncryptedClientIdentification()
|
enc_client_id = EncryptedClientIdentification()
|
||||||
enc_client_id.provider_id = service_certificate.provider_id
|
enc_client_id.provider_id = service_certificate.provider_id
|
||||||
|
|
Loading…
Reference in New Issue