Содержание статьи:
CAS (Conditional Access System / Система условного доступа) — это сервис, разработанный drmnow!, который позволяет индивидуально переопределять ограничения доступа к защищенному контенту на основе запросов с DRM-сервисов.
Примеры использования:
Это руководство поможет вам сделать собственный вариант CAS, отвечающий вашим сценариям использования, и интегрировать его с нашими DRM-сервисами.
DRM-сервис при каждом запросе лицензии отправляет в CAS информацию про исходные заголовки запроса, ключи шифрования контента, прототип ответа с ограничениями для лицензии по умолчанию, а также данные, зависящие от типа DRM. CAS обрабатывает полученные данные, и возвращает прототип ответа с замененными в нем ограничениями обратно в DRM-сервис, после чего тот применяет полученные ограничения к лицензии. Если в итоговых ограничениях воспроизведение разрешено, то генерируется лицензия с новыми ограничениями из CAS, и она возвращается клиенту. В противном случае клиенту возвращается ошибка. Если CAS недоступен или вернул ответ с кодом HTTP 4xx, 5xx, то лицензия также не будет выдана, а клиент получит ошибку.
Этот принцип работы также показан в виде схемы на изображении ниже.
Логика, по которой происходит замена ограничений, определяется вашим сценарием использования, и не является строго фиксированной, она может быть воплощена с использованием любых удобных для вас языков программирования и фреймворков. Однако формат обмена данными при этом должен соответствовать описанному в данном руководстве.
Общий вид информации, отправляемой в CAS (см. детали для каждого типа DRM в разделах «Структура запроса» и ответа и «Примеры»):
{
"original_headers": "[DICTIONARY]",
"key_data": "[LIST OF DICTIONARIES]",
"response_prototype": "[DICTIONARY, MAY CONTAIN LISTS]"
}
Поле original_headers содержит исходные заголовки запроса к DRM-сервису, а также содержит специальное
поле с ключом QUERY_ARGS, значением которого является строка параметров запроса (например, arg1=val1&arg2=val2
).
Поле key_data содержит информацию о ключах: идентификатор контента (Content ID), идентификатор ключа (Key Id), тип трека (если применяется).
Поле response_prototype содержит свойства и ограничения лицензии по умолчанию, сгенерированные DRM-сервисом. Они могут быть переопределены.
Примечание: этот формат отличается для разных DRM-систем и может включать дополнительные поля. Widevine включает поле "parse_only_data", Playready включает поле "client_info". Также в поле "misc" может присутствовать произвольная невложенная информация.
Обмен данными (направление запроса и получение ответа) происходит в JSON-формате. Для отправки и
получения данных используется протокол HTTP. Для передачи запроса с DRM-сервиса к CAS используются
POST-запросы. Соответственно, CAS должен иметь endpoint для приема таких запросов (например http://some.host.tld:port/v2/cas
)
JSON запроса должен содержать следующие поля:
JSON запроса также может содержать проиизвольную невложенную информацию.
Валидное поле original_headers представляет собой непустой невложенный словарь, который ДОЛЖЕН содержать обязательное строковое поле QUERY_ARGS (строка параметров запроса). Пример:
{
"original_headers": {
"host": "127.0.0.1:8000",
"user-agent": "curl/7.68.0",
"accept": "*/*",
"x-project": "[project]",
"content-length": "8346",
"content-type": "application/x-www-form-urlencoded",
"expect": "100-continue",
"X-Project": "[project]",
"QUERY_ARGS": "arg1=val1&arg2=val2"
}
}
Если в запросе строка параметров запроса была пустой, то поле QUERY_ARGS тоже должно быть пустым.
Валидное поле key_data представляет собой непустой список словарей.
Каждый из словарей должен содержать поля content_id
и key_id
. Примечание: для Fairplay key_id необязателен.
content_id - произвольная строка.
key_id - шестнадцатеричная (hex) строка длины 32 (кроме Widevine), либо представление в base64
этой строки (только Widevine), которое может быть получено следующим образом:
$ python3
>>> import base64
>>> import binascii
>>> base64.b64encode(binascii.unhexlify('97ed5004a0d0a59dcc13e1ec26b23177'.encode())).decode()
'l+1QBKDQpZ3ME+HsJrIxdw=='
Здесь '97ed5004a0d0a59dcc13e1ec26b23177' — исходная hex-строка, а 'l+1QBKDQpZ3ME+HsJrIxdw==' — ее представление в base64.
Также может присутствовать необязательное поле track_type (SD, HD, UHD1, UHD, AUDIO).
Пример:
{
"key_data": [
{
"track_type": "UHD1",
"content_id": "abcde",
"key_id": "SBBgssxKQlisxKCRJtBGfw=="
}
]
}
Валидное поле response_prototype представляет собой непустой словарь, содержащий обязательное поле content_key_specs, значением которого является список словарей, структура которых отличается в зависимости от DRM-системы (см. подробности в примерах далее).
Также могут присутствовать другие поля в зависимости от вида DRM.
Примечание: в key_data и response_prototype должно присутствовать одинаковое количество key_id, при этом они должны совпадать (т.е. в response_prototype не могут присутствовать те ключи, которых нет в key_data, и не могут отсутствовать те ключи, которые есть в key_data).
Валидное поле parse_only_data представляет собой непустой словарь. Используется только с Widevine.
Валидное поле client_info представляет собой непустой словарь. Используется только с Playready.
Ответ должен иметь такую же структуру, как поле response_prototype запроса, при этом значения некоторых его полей могут быть переопределены (см. примеры).
Ниже приведены примеры запросов и ответов для разных DRM-систем.
{
"original_headers": {
"host": "127.0.0.1:8000",
"user-agent": "curl/7.68.0",
"accept": "*/*",
"x-project": "[project]",
"content-length": "8346",
"content-type": "application/x-www-form-urlencoded",
"expect": "100-continue",
"X-Project": "[project]",
"QUERY_ARGS": "arg1=val1&arg2=val2"
},
"key_data": [
{
"track_type": "UHD",
"content_id": "ZXhwNTY=",
"key_id": "SBBgssxKQlisxKCRJtBGfw=="
}
],
"parse_only_data": {
"status": "OK",
"status_message": "",
"license_metadata": {
"content_id": "ZXhwNTY=",
"license_type": "STREAMING",
"request_type": "NEW"
},
"supported_tracks": [
{
"type": "UHD1",
"key_id": "qUSciifJUaCcliHbl3zY5w=="
},
{
"type": "HD",
"key_id": "Ex6k7P0WUACFcnLoPcZRAg=="
},
{
"type": "SD",
"key_id": "5o+8VuETV8SEbxEAUtiImA=="
},
{
"type": "AUDIO",
"key_id": "YzEzHAB4WL+bxXpSRzLKKA=="
}
],
"make": "Google",
"model": "ChromeCDM-Linux-x64-4",
"security_level": 3,
"internal_status": 0,
"session_state": {
"license_id": {
"request_id": "YAbdFo8FyLr2PYTSdv5/0Q==",
"session_id": "YAbdFo8FyLr2PYTSdv5/0Q==",
"purchase_id": "",
"type": "STREAMING",
"version": 0,
"original_rental_duration_seconds": 0,
"original_playback_duration_seconds": 0,
"original_start_time_seconds": 1623862478
},
"signing_key": "Pjod4jIVOdkTiu5SP2fRSyHiSDHk2JzmjneksAhR9mJo/QZ7KE5uqqg6mI6L/5kEc6q/U/99HMfeEvXb84Wesw==",
"keybox_system_id": 20121,
"license_counter": 0
},
"drm_cert_serial_number": "NjFhNTZjOTJjNzc0ZDBjMGRmNDViODMzZTQzOTFiYmEwYQ==",
"device_whitelist_state": "DEVICE_NOT_WHITELISTED",
"platform": "linux",
"device_state": "RELEASED",
"pssh_data": {
"key_id": [
"YjAyMjg3NTIwODdjMjM2YzU1YTQ0NTM0YWU3ZGE0MTQ="
],
"content_id": "YmJi"
},
"client_max_hdcp_version": "HDCP_NONE",
"client_info": [
{
"name": "architecture_name",
"value": "x86-64"
},
{
"name": "company_name",
"value": "Google"
},
{
"name": "model_name",
"value": "ChromeCDM"
},
{
"name": "platform_name",
"value": "Linux"
},
{
"name": "widevine_cdm_version",
"value": "4.10.2209.0"
}
],
"signature_expiration_secs": 188559989,
"platform_verification_status": "PLATFORM_UNVERIFIED",
"content_owner": "***",
"content_provider": "***",
"system_id": 20121,
"oem_crypto_api_version": 16,
"resource_rating_tier": 0,
"default_device_security_profiles": {
"profile_name": [
"minimum"
]
},
"service_version_info": {
"license_sdk_version": "16.4.2 Built on Mar 29 2021 23:40:46 (1617086373)",
"license_service_version": "widevine_license_wls_20210302_202486-RC04"
}
},
"response_prototype": {
"content_key_specs": [
{
"key_id": "SBBgssxKQlisxKCRJtBGfw==",
"security_level": 1,
"required_output_protection": {
"hdcp": "HDCP_NONE",
"disable_analog_output": false,
"hdcp_srm_rule": "HDCP_SRM_RULE_NONE"
}
}
],
"session_init": {
"override_device_revocation": true
},
"use_policy_overrides_exclusively": true,
"policy_overrides": {
"license_duration_seconds": 0,
"playback_duration_seconds": 0,
"can_play": true,
"can_persist": false,
"can_renew": false
},
"allow_unverified_platform": true
}
}
заголовки {'User-Agent': 'drmnow! / widevine / 1.1', 'X-Project':
'[project]'}
{
"content_key_specs": [
{
"key_id": "SBBgssxKQlisxKCRJtBGfw==",
"security_level": 3,
"required_output_protection": {
"hdcp": "HDCP_NONE",
"disable_analog_output": false,
"hdcp_srm_rule": "HDCP_SRM_RULE_NONE"
}
}
],
"session_init": {
"override_device_revocation": true
},
"use_policy_overrides_exclusively": true,
"policy_overrides": {
"license_duration_seconds": 3600,
"playback_duration_seconds": 3600,
"can_play": true,
"can_persist": true,
"can_renew": false
},
"allow_unverified_platform": true
}
{
"original_headers": {
"host": "127.0.0.1:8000",
"user-agent": "curl/7.68.0",
"accept": "*/*",
"x-project": "[project]",
"content-length": "8346",
"content-type": "application/x-www-form-urlencoded",
"expect": "100-continue",
"X-Project": "[project]",
"QUERY_ARGS": "arg1=val1&arg2=val2"
},
"key_data": [
{
"track_type": "UHD1",
"content_id": "abcdef",
"key_id": "97ed5004a0d0a59dcc13e1ec26b23177"
}
],
"response_prototype": {
"resultCode": "success",
"contentid": "cHJlcHJvZDI5MDYyMWZpeGVk",
"keyAndPolicy": [
{
"userPolicy": {
"beginDate": 1625057628,
"expirationDate": 1625147628
},
"distributionMode": "VOD",
"keyInfo": {
"keyId": "97ed5004a0d0a59dcc13e1ec26b23177",
"keyEncryptedIV": "7g2GivJ8x+9Q17anjvPPZw=="
},
"contentPolicy": {
"securityLevel": 1,
"licenseType": "PERSISTENT",
"outputControl": 0
}
}
]
}
}
заголовки {'User-Agent': 'drmnow! / wiseplay / 1.1', 'X-Project':
'[project]'}
{
"resultCode": "success",
"contentid": "cHJlcHJvZDI5MDYyMWZpeGVk",
"keyAndPolicy": [
{
"userPolicy": {
"beginDate": 1625057628,
"expirationDate": 1625147628
},
"distributionMode": "VOD",
"keyInfo": {
"keyId": "97ed5004a0d0a59dcc13e1ec26b23177",
"keyEncryptedIV": "7g2GivJ8x+9Q17anjvPPZw=="
},
"contentPolicy": {
"securityLevel": 1,
"licenseType": "PERSISTENT",
"outputControl": 0
}
}
]
}
{
"original_headers": {
"host": "127.0.0.1:8000",
"user-agent": "curl/7.68.0",
"accept": "*/*",
"x-project": "[project]",
"content-length": "8346",
"content-type": "application/x-www-form-urlencoded",
"expect": "100-continue",
"X-Project": "[project]",
"QUERY_ARGS": "arg1=val1&arg2=val2"
},
"key_data": {
"key_data": [
{
"content_id": "content_id"
}
]
},
"response_prototype": {
"content_key_specs": [
{
"content_id": "content_id",
"rental_duration_seconds": 0,
"playback_duration_seconds": 0,
"persistence_is_allowed": false,
"persistence_duration": 0,
"lease_duration": 0,
"force_offline_key_tllv": true
}
]
}
}
заголовки {'User-Agent': 'drmnow! / fairplay / 1.1', 'X-Project':
'[project]'}
{
"content_key_specs": [
{
"content_id": "content_id",
"rental_duration_seconds": 0,
"playback_duration_seconds": 0,
"persistence_is_allowed": false,
"persistence_duration": 0,
"lease_duration": 0,
"force_offline_key_tllv": true
}
]
}
{
"original_headers": {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "*/*",
"Expect": "100-continue",
"Host": "127.0.0.1:8000",
"User-Agent": "curl/7.68.0",
"Content-Length": "9434",
"X-Project": "[project]",
"QUERY_ARGS": "arg1=val1&arg2=val2"
},
"key_data": [
{
"key_id": "c23841e3-be07-507f-7f12-7fdc579663ed",
"content_id": "content_id",
"quality": "legacy"
}
],
"client_info": {
"client_version": "10.0.16384.10011",
"client_supports_v3": true,
"client_security_level": 0
},
"response_prototype": {
"content_key_specs": [
{
"key_id": "c23841e3-be07-507f-7f12-7fdc579663ed",
"can_play": true,
"can_persist": true,
"security_level": "2000",
"license_duration_seconds": 0,
"playback_duration_seconds": 0
}
]
}
}
заголовки {'User-Agent': 'drmnow! / playready / 1.1', 'X-Project':
'[project]'}
{
"content_key_specs": [
{
"key_id": "c23841e3-be07-507f-7f12-7fdc579663ed",
"can_play": true,
"can_persist": true,
"security_level": "3000",
"license_duration_seconds": 3600,
"playback_duration_seconds": 3600
}
]
}