Skip to content

Commit

Permalink
Update for SPEKEv2 for AES 128 and Sample AES (#128)
Browse files Browse the repository at this point in the history
* update code for cmaf sample aes workflow in speke v2

* remove fairplay hls signaling data inputs

* insert iv for all fairplay in speke v2

* add iv for aes128

* fix clearkey path and simplify code

* Update requirements.txt
update zappa from 0.56 to 0.58 to support cryptography v35.0 and above

---------

Co-authored-by: John Meigs <[email protected]>
  • Loading branch information
eddieramirez and John Meigs authored Jun 21, 2024
1 parent 47cf29e commit 5dc9c38
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 35 deletions.
16 changes: 0 additions & 16 deletions cloudformation/speke_reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,6 @@
},
"WIDEVINE_HLS_SIGNALING_DATA_MASTER": {
"Ref": "WidevineHlsSignalingDataMaster"
},
"FAIRPLAY_HLS_SIGNALING_DATA_MEDIA": {
"Ref": "FairplayHlsSignalingDataMedia"
},
"FAIRPLAY_HLS_SIGNALING_DATA_MASTER": {
"Ref": "FairplayHlsSignalingDataMaster"
}
}
},
Expand Down Expand Up @@ -693,16 +687,6 @@
"Default": "",
"Description": "Encoded Widevine HlsSignalingData for master (ext-x-session-key) that is included in the encrypted content and is supported in Speke V2.0 only",
"Type": "String"
},
"FairplayHlsSignalingDataMedia": {
"Default": "",
"Description": "Encoded Fairplay HlsSignalingData for media (ext-x-key) that is included in the encrypted content and is supported in Speke V2.0 only",
"Type": "String"
},
"FairplayHlsSignalingDataMaster": {
"Default": "",
"Description": "Encoded Fairplay HlsSignalingData for master (ext-x-session-key) that is included in the encrypted content and is supported in Speke V2.0 only",
"Type": "String"
}
},
"Conditions": {
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

argcomplete==3.1.1
asn1crypto==1.5.1
astroid==2.15.6
Expand Down Expand Up @@ -50,5 +49,5 @@ Werkzeug==2.3.7
wrapt==1.15.0
wsgi-request-logger==0.4.6
yapf==0.40.1
zappa==0.56.0
zipp==3.16.2
zappa==0.58.0
zipp==3.16.2
47 changes: 31 additions & 16 deletions src/key_server_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@
HLS_AES_128_KEY_FORMAT_VERSIONS = '1' # '1'
HLS_SAMPLE_AES_KEY_FORMAT = 'com.apple.streamingkeydelivery'
HLS_SAMPLE_AES_KEY_FORMAT_VERSIONS = '1'
# speke v2.0 settings for fairplay drm
FAIRPLAY_HLS_SIGNALING_DATA_MEDIA = os.environ["FAIRPLAY_HLS_SIGNALING_DATA_MEDIA"]
FAIRPLAY_HLS_SIGNALING_DATA_MASTER = os.environ["FAIRPLAY_HLS_SIGNALING_DATA_MASTER"]

# settings for widevine drm
WIDEVINE_PSSH_BOX = os.environ["WIDEVINE_PSSH_BOX"]
Expand Down Expand Up @@ -75,6 +72,7 @@ def __init__(self, request_body, cache, generator):
self.document_key = None
self.hmac_key = None
self.public_key = None
self.init_vector = None
self.use_playready_content_key = False
element_tree.register_namespace("cpix", "urn:dashif:org:cpix")
element_tree.register_namespace("pskc", "urn:ietf:params:xml:ns:keyprov:pskc")
Expand Down Expand Up @@ -188,10 +186,16 @@ def fill_request(self):
else:
print("CLEAR-RESPONSE")

for content_key in self.root.findall("./{urn:dashif:org:cpix}ContentKeyList/{urn:dashif:org:cpix}ContentKey"):
self.init_vector = content_key.get("explicitIV")

for drm_system in self.root.findall("./{urn:dashif:org:cpix}DRMSystemList/{urn:dashif:org:cpix}DRMSystem"):
kid = drm_system.get("kid")
system_id = drm_system.get("systemId")
system_ids[system_id] = kid
# HLS SAMPLE AES and AES 128 Only
if self.init_vector is None and (system_id == HLS_SAMPLE_AES_SYSTEM_ID or system_id == CLEAR_KEY_AES_128_SYSTEM_ID):
self.init_vector = base64.b64encode(self.generator.key(content_id, kid)).decode('utf-8')
print("SYSTEM-ID {}".format(system_id.lower()))
self.fixup_document(drm_system, system_id, content_id, kid)

Expand All @@ -200,9 +204,9 @@ def fill_request(self):
init_vector = content_key.get("explicitIV")
data = element_tree.SubElement(content_key, "{urn:dashif:org:cpix}Data")
secret = element_tree.SubElement(data, "{urn:ietf:params:xml:ns:keyprov:pskc}Secret")
# HLS SAMPLE AES Only
if init_vector is None and system_ids.get(HLS_SAMPLE_AES_SYSTEM_ID, False) == kid:
content_key.set('explicitIV', base64.b64encode(self.generator.key(content_id, kid)).decode('utf-8'))
# HLS SAMPLE AES and AES 128 Only
if init_vector is None and (system_ids.get(HLS_SAMPLE_AES_SYSTEM_ID, False) == kid or system_ids.get(CLEAR_KEY_AES_128_SYSTEM_ID, False) == kid):
content_key.set('explicitIV', self.init_vector)
# generate the key
key_bytes = self.generator.key(content_id, kid)
# store to the key in the cache
Expand Down Expand Up @@ -317,18 +321,30 @@ def fixup_document(self, drm_system, system_id, content_id, kid):
self.safe_remove(drm_system, "{urn:dashif:org:cpix}ContentProtectionData")
self.safe_remove(drm_system, "{urn:dashif:org:cpix}PSSH")
self.safe_remove(drm_system, "{urn:dashif:org:cpix}SmoothStreamingProtectionHeaderData")
ext_x_key_uri = self.cache.url(content_id, kid)
method = "SAMPLE-AES"
key_format = HLS_SAMPLE_AES_KEY_FORMAT
key_format_versions = HLS_SAMPLE_AES_KEY_FORMAT_VERSIONS
init_vector = hex(int.from_bytes(base64.b64decode(self.init_vector), byteorder="big"))

ext_x_session_key, ext_x_key = self.clearkey_hls_signaling_data(ext_x_key_uri, method, key_format, key_format_versions, init_vector)

hls_signalling_data_elems = drm_system.findall("{urn:dashif:org:cpix}HLSSignalingData")
if hls_signalling_data_elems:
drm_system.find("{urn:dashif:org:cpix}HLSSignalingData[@playlist='media']").text = FAIRPLAY_HLS_SIGNALING_DATA_MEDIA
drm_system.find("{urn:dashif:org:cpix}HLSSignalingData[@playlist='master']").text = FAIRPLAY_HLS_SIGNALING_DATA_MASTER
drm_system.find("{urn:dashif:org:cpix}HLSSignalingData[@playlist='media']").text = ext_x_key
drm_system.find("{urn:dashif:org:cpix}HLSSignalingData[@playlist='master']").text = ext_x_session_key

elif system_id.lower() == CLEAR_KEY_AES_128_SYSTEM_ID.lower():
ext_x_key_uri = self.cache.url(content_id, kid)
self.safe_remove(drm_system, "{urn:dashif:org:cpix}ContentProtectionData")
self.safe_remove(drm_system, "{urn:dashif:org:cpix}PSSH")
self.safe_remove(drm_system, "{urn:dashif:org:cpix}SmoothStreamingProtectionHeaderData")
ext_x_key_uri = self.cache.url(content_id, kid)
method = "AES-128"
key_format = HLS_AES_128_KEY_FORMAT
key_format_versions = HLS_AES_128_KEY_FORMAT_VERSIONS
init_vector = hex(int.from_bytes(base64.b64decode(self.init_vector), byteorder="big"))

ext_x_session_key, ext_x_key = self.clearkey_aes_128_hls_signaling_data(ext_x_key_uri)
ext_x_session_key, ext_x_key = self.clearkey_hls_signaling_data(ext_x_key_uri, method, key_format, key_format_versions, init_vector)

hls_signalling_data_elems = drm_system.findall("{urn:dashif:org:cpix}HLSSignalingData")
if hls_signalling_data_elems:
Expand Down Expand Up @@ -356,15 +372,14 @@ def get_response(self):
"body": element_tree.tostring(self.root).decode('utf-8')
}

def clearkey_aes_128_hls_signaling_data(self, ext_x_key_uri):
method = "AES-128"
uri = ext_x_key_uri
key_format = HLS_AES_128_KEY_FORMAT
key_format_versions = HLS_AES_128_KEY_FORMAT_VERSIONS

def clearkey_hls_signaling_data(self, uri, method, key_format, key_format_versions, init_vector=None):
# need to fix
ext_x_session_key = '#EXT-X-SESSION-KEY:METHOD={},URI="{}",KEYFORMAT="{}",KEYFORMATVERSIONS="{}"'.format(method, uri, key_format, key_format_versions)
ext_x_key = '#EXT-X-KEY:METHOD={},URI="{}",KEYFORMAT="{}",KEYFORMATVERSIONS="{}"'.format(method, uri, key_format, key_format_versions)
if init_vector is not None:
ext_x_session_key = '{},IV={}'.format(ext_x_session_key, init_vector)
ext_x_key = '{},IV={}'.format(ext_x_key, init_vector)


encoded_session_key = base64.b64encode(ext_x_session_key.encode('utf-8')).decode('utf-8')
encoded_key = base64.b64encode(ext_x_key.encode('utf-8')).decode('utf-8')
Expand Down

0 comments on commit 5dc9c38

Please sign in to comment.