Decouple key fetcher; Update ECMG API

This commit is contained in:
Lu Chen
2020-02-11 18:08:06 -08:00
parent ac564bb46f
commit 77b2fcc678
41 changed files with 1872 additions and 1905 deletions

6
BUILD
View File

@@ -12,18 +12,20 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
pkg_tar(
name = "media_cas_packager_sdk_files",
strip_prefix = "/",
files = [
"//common:binary_release_files",
"//example:binary_release_files",
"//media_cas_packager_sdk/public:binary_release_files",
"//protos/public:binary_release_files",
"//util:binary_release_files",
],
mode = "0644",
strip_prefix = "/",
)
pkg_tar(
name = "media_cas_packager_sdk-bin",
deps = [":media_cas_packager_sdk_files"],
files = ["//media_cas_packager_sdk/public:libmedia_cas_packager_sdk.so"],
mode = "0755",
deps = [":media_cas_packager_sdk_files"],
)

View File

@@ -22,8 +22,6 @@ using oemcrypto_core_message::serialize::
using oemcrypto_core_message::serialize::CreateCoreRenewalResponse;
using widevine::Sha256_Hash;
// TODO(user): Check the Core*RequestFromMessage and
// CreateCore*ResponseFromProto return value when b/148472911 is fixed.
namespace widevine {
namespace core_message_util {
bool GetCoreProvisioningResponse(
@@ -43,29 +41,38 @@ bool GetCoreRenewalOrReleaseLicenseResponse(
const std::string& request_core_message,
std::string* response_core_message) {
oemcrypto_core_message::ODK_RenewalRequest odk_renewal_request;
CoreRenewalRequestFromMessage(request_core_message, &odk_renewal_request);
// TODO(user): This function is going to need to know what the renewal
// license is, and extract the renewal duration. This should be the sum of
// renewal_delay_seconds + 2 * renewal_recovery_duration_seconds. Or if you
// want, we could also create CreateCoreRenewalResponseFromProto -- is that
// better?
uint64_t renewal_duration_seconds = 3600; // I just made this up for now.
CreateCoreRenewalResponse(odk_renewal_request, renewal_duration_seconds,
response_core_message);
return true;
if (request_core_message.empty()) {
return false;
}
if (!CoreRenewalRequestFromMessage(request_core_message,
&odk_renewal_request)) {
return false;
}
// TODO(b/141762043): This function is going to need to know what the
// renewal license is, and extract the renewal duration. This should be the
// sum of renewal_delay_seconds + 2 * renewal_recovery_duration_seconds.
uint64_t renewal_duration_seconds =
3600; // PTAL when addressing b/141762043.
return CreateCoreRenewalResponse(
odk_renewal_request, renewal_duration_seconds, response_core_message);
}
bool GetCoreNewLicenseResponse(const std::string& license,
const std::string& request_core_message,
const bool nonce_required,
std::string* response_core_message) {
if (request_core_message.empty()) {
return false;
}
oemcrypto_core_message::ODK_LicenseRequest odk_license_request;
CoreLicenseRequestFromMessage(request_core_message, &odk_license_request);
if (!CoreLicenseRequestFromMessage(request_core_message,
&odk_license_request)) {
return false;
}
std::string core_request_sha256 = Sha256_Hash(request_core_message);
CreateCoreLicenseResponseFromProto(license, odk_license_request,
core_request_sha256, nonce_required,
response_core_message);
return true;
return CreateCoreLicenseResponseFromProto(license, odk_license_request,
core_request_sha256, nonce_required,
response_core_message);
}
} // namespace core_message_util

View File

@@ -46,6 +46,7 @@ cc_binary(
srcs = ["wv_cas_ecm_example.cc"],
deps = [
"//base",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_ecm",
"//media_cas_packager_sdk/public:wv_cas_types",
],
@@ -55,12 +56,8 @@ cc_binary(
name = "wv_cas_key_fetcher_example",
srcs = ["wv_cas_key_fetcher_example.cc"],
deps = [
"//base",
"@abseil_repo//absl/flags:flag",
"@abseil_repo//absl/flags:parse",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//protos/public:media_cas_encryption_cc_proto",
],
)

View File

@@ -27,43 +27,44 @@ constexpr char kTestEcmgChannelSetup[] = {
};
constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = {
'\x03', // protocol_version
'\x00', '\x01', // message_type - Channel_setup
'\x00', '\x70', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x01', // parameter_type- SUPER_CAS_ID
'\x00', '\x04', // parameter_length
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
'\x80', '\x00', // parameter_type - AGE_RESTRICTION
'\x00', '\x01', // parameter_length
'\x00', // parameter_value
'\x80', '\x01', // parameter_type - CRYPTO_MODE
'\x00', '\x07', // parameter_length
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
'\x80', '\x04', // parameter_type - TRACK_TYPES
'\x00', '\x02', // parameter_length
'S', 'D', // parameter_value
'\x80', '\x04', // parameter_type - TRACK_TYPES
'\x00', '\x02', // parameter_length
'H', 'D', // parameter_value
'\x80', '\x02', // parameter_type - CONTENT_ID
'\x00', '\x09', // parameter_length
'C', 'a', 's', 'T', 's', 'F', 'a', 'k',
'e', // parameter_value - CasTsFake
'\x80', '\x03', // parameter_type - CONTENT_PROVIDER
'\x00', '\x0d', // parameter_length
'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e',
'_', 't', 'e', 's', 't', // parameter_value - widevine_test
'\x80', '\x06', // parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x80', '\x06', // parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
'\x03', // protocol_version
'\x00', '\x01', // message_type - Channel_setup
'\x00', '\x8c', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x01', // parameter_type- SUPER_CAS_ID
'\x00', '\x04', // parameter_length
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
'\x80', '\x00', // parameter_type - AGE_RESTRICTION
'\x00', '\x01', // parameter_length
'\x00', // parameter_value
'\x80', '\x01', // parameter_type - CRYPTO_MODE
'\x00', '\x07', // parameter_length
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
'\x80', '\x04', // parameter_type - TRACK_TYPES
'\x00', '\x02', // parameter_length
'S', 'D', // parameter_value
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
'\x00', '\x30', // parameter_length
// parameter_value - ENTITLEMENT_ID (16 bytes)
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
'\x00', '\x30', // parameter_length
// parameter_value - ENTITLEMENT_ID (16 bytes)
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'};
constexpr char kTestEcmgChannelTest[] = {
'\x03', // protocol_version
@@ -74,6 +75,18 @@ constexpr char kTestEcmgChannelTest[] = {
'\x00', '\x01', // parameter_value
};
constexpr char kTestEcmgChannelError[] = {
'\x03', // protocol_version
'\x00', '\x05', // message_type - Stream_error
'\x00', '\x0c', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x70', '\x00', // parameter_type - Error_status
'\x00', '\x02', // parameter_length
'\x70', '\x00', // parameter_value (UNKNOWN_ERROR)
};
constexpr char kTestEcmgChannelStatus[] = {
'\x03', // protocol_version
'\x00', '\x03', // message_type - Channel_status
@@ -113,7 +126,7 @@ constexpr char kTestEcmgChannelStatus[] = {
constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
'\x03', // protocol_version
'\x01', '\x01', // message_type - Stream_setup
'\x00', '\x1e', // message_length
'\x00', '\x46', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
@@ -129,7 +142,14 @@ constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
'\x80', '\x05', // parameter_type - STREAM_TRACK_TYPE
'\x00', '\x02', // parameter_length
'S', 'D', // parameter_value
};
'\x80', '\x06', // parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x80', '\x06', // parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
constexpr char kTestEcmgStreamSetup[] = {
'\x03', // protocol_version
@@ -179,6 +199,24 @@ constexpr char kTestEcmgStreamStatus[] = {
'\x01' // parameter_value
};
constexpr char kTestEcmgStreamError[] = {
'\x03', // protocol_version
'\x01', '\x06', // message_type - Stream_error
'\x00', '\x1a', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x0f', // parameter_type - ECM_stream_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x70', '\x00', // parameter_type - Error_status
'\x00', '\x02', // parameter_length
'\x70', '\x00', // parameter_value (UNKNOWN_ERROR)
'\x70', '\x01', // parameter_type - Error_information
'\x00', '\x04', // parameter_length
'i', 'n', 'f', 'o' // parameter_value
};
constexpr char kTestEcmgCwProvision[] = {
'\x03', // protocol_version
'\x02', '\x01', // message_type - CW_provision
@@ -209,7 +247,7 @@ constexpr char kTestEcmgCwProvision[] = {
constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
'\x03', // protocol_version
'\x02', '\x01', // message_type - CW_provision
'\x00', '\xaa', // message_length
'\x00', '\xf4', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
@@ -225,43 +263,55 @@ constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
'\x00', '\x14', // parameter_type - CP_CW_Combination
'\x00', '\x12', // parameter_length
'\x00', '\x00', // parameter_value - CP (2 bytes) then CW next (16 bytes)
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', //
'\x00', '\x14', // parameter_type - CP_CW_Combination
'\x00', '\x12', // parameter_length
'\x00', '\x01', // parameter_value - CP (2 bytes) then CW next (16 bytes)
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', //
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', //
'\x00', '\x0d', // parameter_type - access_criteria
'\x00', '\x62', // parameter_length
'\x00', '\xac', // parameter_length
'\x80', '\x00', // access_criteria parameter_type - AGE_RESTRICTION
'\x00', '\x01', // parameter_length
'\x00', // parameter_value
'\x80', '\x01', // access_criteria parameter_type - CRYPTO_MODE
'\x00', '\x07', // parameter_length
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
'\x80', '\x04', // access_criteria parameter_type - TRACK_TYPES
'\x00', '\x02', // parameter_length
'S', 'D', // parameter_value
'S', 'D', // parameter_value
'\x80', '\x05', // access_criteria parameter_type - STREAM_TRACK_TYPE
'\x00', '\x02', // parameter_length
'S', 'D', // parameter_value
'\x80', '\x02', // access_criteria parameter_type - CONTENT_ID
'\x00', '\x09', // parameter_length
'C', 'a', 's', 'T', 's', 'F', 'a', 'k',
'e', // parameter_value - CasTsFake
'\x80', '\x03', // access_criteria parameter_type - CONTENT_PROVIDER
'\x00', '\x0d', // parameter_length
'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e',
'_', 't', 'e', 's', 't', // parameter_value - widevine_test
'S', 'D', // parameter_value
'\x80', '\x06', // access_criteria parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', //
'\x80', '\x06', // access_criteria parameter_type - CONTENT_IV
'\x00', '\x10', // parameter_length
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'};
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', //
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', //
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
'\x00', '\x30', // parameter_length
// parameter_value - ENTITLEMENT_ID (16 bytes)
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', //
'\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION
'\x00', '\x30', // parameter_length
// parameter_value - ENTITLEMENT_ID (16 bytes)
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
// parameter_value (continued) - ENTITLEMENT_KEY (32 bytes)
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', //
'\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'};
constexpr char kTestEcmgCwProvisionSingleKey[] = {
'\x03', // protocol_version
@@ -303,25 +353,25 @@ constexpr char kTestEcmgEcmResponse[] = {
'\x00', '\xbc', // parameter_length
// parameter_value - ECM_datagram
'\x47', '\x40', '\x00', '\x10', '\x00', '\x80', '\x70', '\xa5', '\x4a',
'\xd4', '\x02', '\x0b', '\xc0', '\x66', '\x61', '\x6b', '\x65', '\x5f',
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x31', '\x2e', '\x2e',
'\x2e', '\x2e', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd',
'\xd4', '\x02', '\x0b', '\xc0', '\x30', '\x31', '\x32', '\x33', '\x34',
'\x35', '\x36', '\x37', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35',
'\x36', '\x37', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd',
'\x34', '\x73', '\xfe', '\x5d', '\x1c', '\x65', '\xa0', '\xbf', '\x93',
'\xfe', '\x01', '\x4b', '\x1d', '\xcd', '\x9e', '\x1d', '\x3a', '\x36',
'\x99', '\x8f', '\x47', '\xa1', '\x3b', '\x46', '\xf1', '\xde', '\x9e',
'\xc2', '\x88', '\xf8', '\x27', '\x2f', '\xea', '\xa1', '\x63', '\x9b',
'\x1b', '\x6a', '\x56', '\x2d', '\x26', '\x31', '\x32', '\x33', '\x34',
'\x35', '\x36', '\x37', '\x38', '\x66', '\x61', '\x6b', '\x65', '\x5f',
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x32', '\x2e', '\x2e',
'\x2e', '\x2e', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14',
'\x4d', '\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34',
'\xb4', '\xfd', '\xbe', '\x86', '\x21', '\x35', '\x1e', '\xda', '\x81',
'\x89', '\x6f', '\x70', '\xd3', '\xd2', '\xb2', '\x79', '\xf2', '\xcd',
'\xeb', '\xc5', '\xaf', '\x89', '\xab', '\xeb', '\xf0', '\x1b', '\xd0',
'\xd3', '\xe9', '\x7d', '\x81', '\x8a', '\x31', '\x32', '\x33', '\x34',
'\x35', '\x36', '\x37', '\x38', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\x90', '\x04', '\x1a', '\xab', '\x1a', '\x46', '\x00', '\xdb', '\xdb',
'\x2f', '\xcb', '\xa5', '\x10', '\x2a', '\x3b', '\x73', '\xf1', '\xfe',
'\x9d', '\x28', '\x2a', '\x3c', '\x0a', '\x5c', '\x58', '\xbc', '\x97',
'\x1d', '\x81', '\x5b', '\x5b', '\xf7', '\x00', '\x01', '\x02', '\x03',
'\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c',
'\x0d', '\x0e', '\x0f', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66',
'\x67', '\x68', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
'\x68', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14', '\x4d',
'\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34', '\x84',
'\xe7', '\xe8', '\xf5', '\x02', '\x39', '\x1f', '\x62', '\x43', '\xb5',
'\xca', '\xb0', '\xee', '\x77', '\xaf', '\xe9', '\x84', '\x41', '\x0a',
'\x16', '\x45', '\x23', '\x6c', '\x1d', '\xe7', '\xdb', '\xfd', '\x91',
'\x91', '\xac', '\x47', '\xbc', '\x10', '\x11', '\x12', '\x13', '\x14',
'\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d',
'\x1e', '\x1f', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'};
constexpr char kTestEcmgStreamClose[] = {

View File

@@ -73,6 +73,42 @@ const char kTestEmmgStreamSetup[] = {
'\x01' // parameter_value - private data
};
const char kTestEmmgStreamBwRequest[] = {
'\x02', // protocol_version
'\x01', '\x17', // message_type - Stream_BW_request
'\x00', '\x1a', // message_length
'\x00', '\x01', // parameter_type - client_id
'\x00', '\x04', // parameter_length
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
'\x00', '\x03', // parameter_type - data_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x04', // parameter_type - data_stream_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x06', // parameter_type - bandwidth
'\x00', '\x02', // parameter_length
'\x00', '\x64' // parameter_value
};
const char kTestEmmgStreamBwAllocation[] = {
'\x02', // protocol_version
'\x01', '\x18', // message_type - Stream_BW_allocation
'\x00', '\x1a', // message_length
'\x00', '\x01', // parameter_type - client_id
'\x00', '\x04', // parameter_length
'\x4a', '\xd4', '\x00', '\x00', // parameter_value
'\x00', '\x03', // parameter_type - data_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x04', // parameter_type - data_stream_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
'\x00', '\x06', // parameter_type - bandwidth
'\x00', '\x02', // parameter_length
'\x00', '\x32' // parameter_value (50 kbps)
};
const char kTestEmmgDataProvision[] = {
'\x02', // protocol_version
'\x02', '\x11', // message_type - Data_provision

View File

@@ -17,63 +17,125 @@
#include <string>
#include <cstdint>
#include "common/status.h"
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
const size_t kContentIvSize = 8;
const bool kKeyRotation = false; // whether key rotation is enabled
const char kCryptoMode[] = "CTR"; // CBC, CTR, or CSA2
const int kEcmPid = 149; // PID for the ECM packet
const size_t kContentIvSize = 16; // 8 or 16
const bool kKeyRotation = true; // whether key rotation is enabled
const char kCryptoMode[] =
"AesScte"; // "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte"
const int kEcmPid = 149; // PID for the ECM packet
const int kAgeRestriction = 0; // Age restriction for the ECM
const char kOutputFile[] =
"/tmp/ecm.ts"; // ECM TS packet will be output to here
"/tmp/ecm.ts"; // ECM TS packet will be output to here
const uint8_t kTableId = 0x80; // 0x80 or 0x81
const char kDefaultTrackTypeSd[] = "SD";
const char kCsaEvenKey[] = "even_key"; // 8 bytes
const char kEvenKey[] = "even_key........"; // 16 bytes
const char kEvenKeyId[] = "even_key_id....."; // 16 bytes
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
const char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes
const char kEvenEntitlementKey[] =
"fakefakefakefakefakefakefake1..."; // 32 bytes
const char kCsaOddKey[] = "odd_key."; // 8 bytes
"fakefakefakefakefakefakefake1..."; // 32 bytes
const char kEvenWrapIv[] = "even_warp_iv...."; // 16 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kOddKeyId[] = "odd_key_id......"; // 16 bytes
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
const char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes
const char kOddEntitlementKey[] =
"fakefakefakefakefakefakefake2..."; // 32 bytes
const uint8_t kTableId = 0x80;
"fakefakefakefakefakefakefake2..."; // 32 bytes
const char kOddWrapIv[] = "odd_warp_iv....."; // 16 bytes
const size_t kTsPacketSize = 188;
widevine::cas::CryptoMode GetCryptoMode(const std::string& crypto_mode) {
if (crypto_mode.compare("CBC") == 0) {
return widevine::cas::CryptoMode::kAesCbc;
using widevine::cas::EntitlementKeyInfo;
using widevine::cas::WvCasContentKeyInfo;
using widevine::cas::WvCasEcmParameters;
WvCasEcmParameters CreateWvCasEcmParameters(bool key_rotation,
int content_iv_size) {
WvCasEcmParameters params;
params.content_iv_size = content_iv_size == 8
? widevine::cas::kIvSize8
: widevine::cas::kIvSize16;
params.key_rotation_enabled = key_rotation;
if (!widevine::cas::StringToCryptoMode(kCryptoMode,
&params.crypto_mode)) {
std::cerr << "Unsupported crypto mode " << kCryptoMode << std::endl;
}
if (crypto_mode.compare("CTR") == 0) {
return widevine::cas::CryptoMode::kAesCtr;
params.age_restriction = kAgeRestriction;
return params;
}
std::vector<EntitlementKeyInfo> CreateInjectedEntitlements(bool key_rotation) {
std::vector<EntitlementKeyInfo> injected_entitlements;
injected_entitlements.reserve(key_rotation ? 2 : 1);
injected_entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
entitlement->key_id = kEvenEntitlementKeyId;
entitlement->key_value = kEvenEntitlementKey;
entitlement->is_even_key = true;
entitlement->track_type = kDefaultTrackTypeSd;
if (key_rotation) {
injected_entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
entitlement->key_id = kOddEntitlementKeyId;
entitlement->key_value = kOddEntitlementKey;
entitlement->is_even_key = false;
entitlement->track_type = kDefaultTrackTypeSd;
}
return widevine::cas::CryptoMode::kDvbCsa2;
return injected_entitlements;
}
std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
int content_iv_size) {
std::vector<WvCasContentKeyInfo> content_keys;
content_keys.reserve(key_rotation ? 2 : 1);
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kEvenKey;
content_key->key_id = kEvenKeyId;
content_key->content_iv =
content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes;
content_key->wrapped_key_iv = kEvenWrapIv;
if (key_rotation) {
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kOddKey;
content_key->key_id = kOddKeyId;
content_key->content_iv =
content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes;
content_key->wrapped_key_iv = kOddWrapIv;
}
return content_keys;
}
int main(int argc, char** argv) {
// Generate ECM.
widevine::cas::WvCasEcm wv_cas_ecm;
widevine::cas::WvCasStatus status = wv_cas_ecm.Initialize(
kContentIvSize, kKeyRotation, GetCryptoMode(kCryptoMode));
if (status != widevine::cas::OK) {
std::cerr << "Failed to initialize WV CAS ECM, error: "
<< widevine::cas::GetWvCasStatusMessage(status)
<< std::endl;
}
WvCasEcmParameters params =
CreateWvCasEcmParameters(kKeyRotation, kContentIvSize);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(kKeyRotation);
widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements);
std::vector<WvCasContentKeyInfo> content_keys =
CreateContentKeyInfo(kKeyRotation, kContentIvSize);
std::string ecm;
widevine::Status status;
if (kKeyRotation) {
status = wv_cas_ecm.GenerateEcm(
kCsaEvenKey, kEvenContentIv8Bytes, kEvenEntitlementKeyId,
kEvenEntitlementKey, kCsaOddKey, kOddContentIv8Bytes,
kOddEntitlementKeyId, kOddEntitlementKey, &ecm);
status = wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, &ecm);
} else {
status = wv_cas_ecm.GenerateSingleKeyEcm(kCsaEvenKey, kEvenContentIv8Bytes,
kEvenEntitlementKeyId,
kEvenEntitlementKey, &ecm);
status = wv_cas_ecm.GenerateSingleKeyEcm(content_keys[0],
kDefaultTrackTypeSd, &ecm);
}
if (status != widevine::cas::OK) {
std::cerr << "Failed to generate WV CAS ECM, error: "
<< widevine::cas::GetWvCasStatusMessage(status)
if (!status.ok()) {
std::cerr << "Failed to generate WV CAS ECM, error: " << status
<< std::endl;
return -1;
} else {
@@ -89,8 +151,8 @@ int main(int argc, char** argv) {
uint8_t continuity_counter; // not used.
status = wv_cas_ecm.GenerateTsPacket(ecm, kEcmPid, kTableId,
&continuity_counter, packet);
if (status != widevine::cas::OK) {
std::cerr << "Failed to create ECM TS packet" << std::endl;
if (!status.ok()) {
std::cerr << "Failed to create ECM TS packet: " << status << std::endl;
return -1;
} else {
std::cout << "TS packet bytes: ";

View File

@@ -7,65 +7,86 @@
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <string>
#include <vector>
#include "glog/logging.h"
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "common/status.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include "protos/public/media_cas_encryption.pb.h"
ABSL_FLAG(std::string, content_id, "21140844", "Content ID");
ABSL_FLAG(bool, key_rotation, true, "Whether key rotation is enabled");
ABSL_FLAG(std::string, track_type, "SD", "Provider name");
const char kContentId[] = "21140844";
const char kContentProvider[] = "widevine";
const char kTrackType[] = "SD";
const bool kKeyRotation = false;
const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
int main(int argc, char **argv) {
absl::ParseCommandLine(argc, argv);
CHECK(!absl::GetFlag(FLAGS_content_id).empty() &&
!absl::GetFlag(FLAGS_track_type).empty())
<< "Flags 'content_id' and 'track_type' are required";
// Required flags in key fetcher.
CHECK(!absl::GetFlag(FLAGS_license_server).empty() &&
!absl::GetFlag(FLAGS_signing_provider).empty() &&
!absl::GetFlag(FLAGS_signing_key).empty() &&
!absl::GetFlag(FLAGS_signing_iv).empty())
<< "Flags 'license_server', 'signing_provider', 'signing_key' "
"and 'signing_iv' are required";
using widevine::Status;
using widevine::cas::EntitlementKeyInfo;
using widevine::cas::EntitlementRequestParams;
using widevine::cas::WvCasKeyFetcher;
class ExampleKeyFetcher : public WvCasKeyFetcher {
public:
ExampleKeyFetcher(const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv)
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
// An example that always returns the same response.
Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const override {
*http_response_json = kHttpResponse;
return widevine::OkStatus();
}
};
int main(int argc, char** argv) {
// Initialize key fetcher.
ExampleKeyFetcher key_fetcher(kSigningProvider, kSingingKey, kSingingIv);
// Create request string.
std::string request_str;
widevine::CasEncryptionRequest request;
request.set_provider(absl::GetFlag(FLAGS_signing_provider));
request.set_content_id(absl::GetFlag(FLAGS_content_id));
request.set_key_rotation(absl::GetFlag(FLAGS_key_rotation));
// Only 1 track in this example.
request.add_track_types(absl::GetFlag(FLAGS_track_type));
LOG(INFO) << "Request: " << request.ShortDebugString();
if (!request.SerializeToString(&request_str)) {
LOG(ERROR) << "Failed to serialize request";
return -1;
}
std::string signed_response_str;
widevine::cas::WvCasKeyFetcher key_fetcher;
widevine::Status status =
key_fetcher.RequestEntitlementKey(request_str, &signed_response_str);
EntitlementRequestParams request_params;
request_params.content_id = kContentId;
request_params.content_provider = kContentProvider;
request_params.track_types = {kTrackType};
request_params.key_rotation = kKeyRotation;
Status status =
key_fetcher.CreateEntitlementRequest(request_params, &request_str);
if (!status.ok()) {
LOG(ERROR) << "Failed to request entitlement key";
return -1;
std::cerr << "Failed to create entitlement request, error: " << status
<< std::endl;
}
widevine::SignedCasEncryptionResponse signed_response;
if (!signed_response.ParseFromString(signed_response_str)) {
LOG(ERROR) << "Failed to deserialize signed response";
return -1;
std::cout << "Request: " << request_str << std::endl;
// Request entitlement keys.
std::string signed_response_str;
status = key_fetcher.MakeHttpRequest(request_str, &signed_response_str);
if (!status.ok()) {
std::cerr << "Failed to request entitlement key, error: " << status
<< std::endl;
}
LOG(INFO) << "Signed response: " << signed_response.ShortDebugString();
widevine::CasEncryptionResponse response;
if (!response.ParseFromString(signed_response.response())) {
LOG(ERROR) << "Failed to deserialize response";
return -1;
std::cout << "Response: " << signed_response_str << std::endl;
// Parse entitlement key response.
std::vector<widevine::cas::EntitlementKeyInfo> entitlements;
status =
key_fetcher.ParseEntitlementResponse(signed_response_str, &entitlements);
if (!status.ok()) {
std::cerr << "Failed to parse entitlement response, error: " << status
<< std::endl;
}
LOG(INFO) << "Response: " << response.ShortDebugString();
std::cout << "Parsed: " << entitlements.size() << " entitlement keys."
<< std::endl;
return 0;
}

View File

@@ -25,13 +25,13 @@ cc_library(
srcs = ["ecm.cc"],
hdrs = ["ecm.h"],
deps = [
":util",
"//base",
"@abseil_repo//absl/strings",
"//common:aes_cbc_util",
"//common:status",
"//common:string_util",
"//media_cas_packager_sdk/public:wv_cas_types",
"//protos/public:media_cas_encryption_cc_proto",
],
)
@@ -41,8 +41,8 @@ cc_test(
srcs = ["ecm_test.cc"],
deps = [
":ecm",
":mpeg2ts",
"//testing:gunit_main",
"//protos/public:media_cas_encryption_cc_proto",
],
)
@@ -63,6 +63,9 @@ cc_test(
srcs = ["ecm_generator_test.cc"],
deps = [
":ecm_generator",
":fixed_key_fetcher",
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"//common:aes_cbc_util",
@@ -80,8 +83,6 @@ cc_library(
],
deps = [
":ecm",
":fixed_key_fetcher",
":key_fetcher",
":mpeg2ts",
":simulcrypt_util",
":util",
@@ -92,8 +93,6 @@ cc_library(
"//common:crypto_util",
"//common:random_util",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_ecm",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//media_cas_packager_sdk/public:wv_cas_types",
],
)
@@ -129,6 +128,7 @@ cc_library(
"//base",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/strings:str_format",
"@abseil_repo//absl/time",
"//common:status",
"//protos/public:media_cas_cc_proto",
],
@@ -156,20 +156,14 @@ cc_library(
"fixed_key_fetcher.h",
],
deps = [
":key_fetcher",
"//base",
"//external:protobuf",
"//common:status",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//protos/public:media_cas_encryption_cc_proto",
],
)
cc_library(
name = "key_fetcher",
hdrs = [
"key_fetcher.h",
],
deps = ["//common:status"],
)
cc_library(
name = "mpeg2ts",
hdrs = [

View File

@@ -16,7 +16,8 @@
#include "absl/strings/str_cat.h"
#include "common/aes_cbc_util.h"
#include "common/string_util.h"
#include "protos/public/media_cas_encryption.pb.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/util.h"
namespace widevine {
namespace cas {
@@ -112,63 +113,14 @@ bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
} // namespace
Status Ecm::Initialize(const std::string& content_id,
const std::string& content_provider,
const EcmInitParameters& ecm_init_parameters,
std::string* key_request_message) {
if (initialized_) {
return {error::INTERNAL, "Already initialized."};
}
if (content_id.empty()) {
return {error::INVALID_ARGUMENT, "Content ID is empty."};
}
if (content_provider.empty()) {
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
}
if (key_request_message == nullptr) {
return {error::INVALID_ARGUMENT, "key_request_message is null."};
}
if (ecm_init_parameters.track_types.empty()) {
return {error::INVALID_ARGUMENT,
"Parameter track_ids must be set with one or more Track IDs."};
}
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
&content_iv_size_)) {
return {error::INVALID_ARGUMENT,
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
}
crypto_mode_ = ecm_init_parameters.crypto_mode;
content_id_ = content_id;
content_provider_ = content_provider;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
track_types_ = ecm_init_parameters.track_types;
age_restriction_ = ecm_init_parameters.age_restriction;
// Construct and return CasEncryptionRequest message for caller to use.
Status status = CreateEntitlementRequest(key_request_message);
if (!status.ok()) {
LOG(ERROR) << "Entitlement request message could not be created.";
return status;
}
// Everything is set up except entitlement keys.
ClearEntitlementKeys();
initialized_ = true;
return OkStatus();
}
Status Ecm::Initialize(
const EcmInitParameters& ecm_init_parameters,
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements) {
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
if (initialized_) {
return {error::INTERNAL, "Already initialized."};
}
if (ecm_init_parameters.track_types.empty()) {
return {error::INVALID_ARGUMENT,
"Parameter track_ids must be set with one or more Track IDs."};
if (injected_entitlements.empty()) {
return {error::NOT_FOUND, "Empty injected entitlements."};
}
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
&content_iv_size_)) {
@@ -178,7 +130,6 @@ Status Ecm::Initialize(
crypto_mode_ = ecm_init_parameters.crypto_mode;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
track_types_ = ecm_init_parameters.track_types;
age_restriction_ = ecm_init_parameters.age_restriction;
ClearEntitlementKeys();
@@ -197,16 +148,6 @@ Status Ecm::Initialize(
return OkStatus();
}
Status Ecm::ProcessCasEncryptionResponse(const std::string& response) {
if (!initialized_) {
return {error::INTERNAL, "Not initialized."};
}
if (response.empty()) {
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
}
return ParseEntitlementResponse(response);
}
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const std::string& track_type,
std::string* serialized_ecm) const {
@@ -278,6 +219,18 @@ Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
return OkStatus();
}
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id,
uint8_t* continuity_counter, uint8_t* packet) {
ssize_t bytes_modified = 0;
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
packet, &bytes_modified);
if (!status.ok() || bytes_modified != kTsPacketSize) {
memset(packet, 0, kTsPacketSize);
return {error::INTERNAL, "Failed to generate TS packet"};
}
return OkStatus();
}
Status Ecm::WrapEntitledKeys(const std::string& track_type,
const std::vector<EntitledKeyInfo*>& keys) const {
if (!initialized_) {
@@ -451,103 +404,6 @@ std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) const {
return serialized_ecm;
}
Status Ecm::CreateEntitlementRequest(std::string* request_string) const {
CasEncryptionRequest request;
request.set_content_id(content_id_);
request.set_provider(content_provider_);
request.set_key_rotation(paired_keys_required_);
// Add labels for tracks.
for (const auto& track_type : track_types_) {
request.add_track_types(track_type);
}
if (!request.SerializeToString(request_string)) {
request_string->clear();
return {error::INTERNAL, "Failure serializing request."};
}
return OkStatus();
}
Status Ecm::ParseEntitlementResponse(const std::string& response_string) {
// TODO(user): parse valid response. NOT Implemented.
ClearEntitlementKeys();
SignedCasEncryptionResponse signed_response;
if (!signed_response.ParseFromString(response_string)) {
return {error::INTERNAL, "Failure parsing signed response."};
}
// TODO(user): Should verify signature.
CasEncryptionResponse response;
if (!response.ParseFromString(signed_response.response())) {
return {error::INTERNAL, "Failure parsing signed response."};
}
if (response.status() != CasEncryptionResponse::OK) {
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
response.status(), " : ",
response.status_message()));
}
if (content_id_ != response.content_id()) {
return Status(
error::INTERNAL,
absl::StrCat("Content ID mismatch in Entitlement Response - expected: ",
content_id_, " received: ", response.content_id()));
}
if (response.entitlement_keys().empty()) {
return {error::INTERNAL, "Failure: no entitlement keys in response."};
}
size_t keys_needed = (paired_keys_required_ ? 2 : 1) * track_types_.size();
if (keys_needed > response.entitlement_keys().size()) {
return Status(
error::INTERNAL,
absl::StrCat(
"Wrong number of keys in Entitlement Response - expected: ",
keys_needed, " got: ", response.entitlement_keys().size()));
}
// Scan available entitlement keys for the ones that are needed.
// For non-key-rotation, this is a key with a track type and not even or odd.
// For key rotation, this is a key with a track type and even or odd.
ClearEntitlementKeys();
for (const auto& key : response.entitlement_keys()) {
if (!key.has_track_type()) {
LOG(WARNING) << "Entitlement key missing track type, skipped.";
// No track ID. Skip it.
continue;
}
EntitlementKeyInfo ekey;
ekey.key_id = key.key_id();
ekey.key_value = key.key();
Status status = ValidateKeyValue(key.key(), kWrappingKeySizeBytes);
if (!status.ok()) {
return status;
}
// Using only keys with correct KeySlot
if (!key.has_key_slot()) {
LOG(WARNING) << "Entitlement key missing key slot, skipped.";
continue;
}
if (paired_keys_required()) {
if (key.key_slot() == CasEncryptionResponse::KeyInfo::EVEN) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
} else if (key.key_slot() == CasEncryptionResponse::KeyInfo::ODD) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey);
}
} else {
if (key.key_slot() == CasEncryptionResponse::KeyInfo::SINGLE) {
PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey);
}
}
}
if (!CheckEntitlementKeys()) {
LOG(ERROR) << "Could not stage entitlement keys from response:";
response.ShortDebugString();
return Status(error::INTERNAL, "No suitable entitlement key was found.");
}
return OkStatus();
}
size_t Ecm::CountEntitlementKeys() const {
size_t count = 0;
for (const auto& track : entitlement_keys_) {
@@ -557,15 +413,6 @@ size_t Ecm::CountEntitlementKeys() const {
}
bool Ecm::CheckEntitlementKeys() const {
// TODO(user): Cross-check entitlement_keys_ track types with track_types_.
if (track_types_.size() > CountEntitlementTracks()) {
// One or more tracks are missing.
LOG(ERROR)
<< "Entitlement keys for one or more tracks is missing - expected "
<< track_types_.size() << " tracks, received "
<< CountEntitlementTracks() << "tracks.";
return false;
}
for (const auto& track : entitlement_keys_) {
if (track.second.size() < (paired_keys_required() ? 2 : 1)) {
LOG(ERROR) << " Wrong number of entitlement keys for track "

View File

@@ -38,12 +38,6 @@ struct EntitledKeyInfo {
std::string wrapped_key_iv;
};
// Declare Content IV size in an ECM stream.
// Content IVs may be encoded as 8 or 16 random bytes. The receiver is
// responsible for appending 8 zeros to an 8-byte IV. All content IVs, once the
// size is declared, must be the same size. Wrapped key IVs are always 16 bytes.
enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// Information needed to start a new ECM stream.
// Fields:
// |content_iv_size| size of all content key IVs in the ECM stream.
@@ -52,25 +46,17 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// |crypto_mode| the encryption mode used for the content stream.
// A constant of type CryptoMode.
// |track_types| a vector of track ID (std::string) that specify the set of track
// types of interest; controls the entitlement keys returned by the server.
// types of interest. |track_types| is deprecated and is ignored by
// Ecm::Initialize().
// |age_restriction| minimum age required; the value represents actual age.
struct EcmInitParameters {
EcmIvSize content_iv_size = kIvSize8;
bool key_rotation_enabled = true;
CryptoMode crypto_mode = CryptoMode::kAesCtr;
std::vector<std::string> track_types;
std::vector<std::string> track_types; // deprecated.
uint8_t age_restriction = 0;
};
// Information needed for the injected entitlement keys. Used for Ecm
// initialization.
struct InjectedEntitlementKeyInfo {
std::string track_type;
bool is_even_key;
std::string key_id; // must be 16 bytes.
std::string key_value; // must be 32 bytes.
};
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
// Transport Stream packet payload of an ECM containing key information for
// decrypting Widevine CAS encrypted content. The keys in the ECM are wrapped
@@ -90,22 +76,6 @@ class Ecm {
Ecm& operator=(const Ecm&) = delete;
virtual ~Ecm() = default;
// Perform initialization for a new ECM stream.
// Args:
// |content_id| uniquely identifies the content (with |content_provider|)
// |content_provider| unique std::string for provider of the content stream.
// |ecm_init_parameters| encryption-related parameters for configuring
// the ECM stream.
// |key_request_message| pointer to a std::string to receive a CasEncryptionRequest
// message.
// Notes:
// The returned |key_request_message| must be sent to the server and
// the response correctly parsed before ECMs can be generated.
virtual Status Initialize(const std::string& content_id,
const std::string& content_provider,
const EcmInitParameters& ecm_init_parameters,
std::string* key_request_message);
// Perform initialization for a new ECM stream with injected entitlement keys.
// Args:
// |ecm_init_parameters| encryption-related parameters for configuring
@@ -114,15 +84,7 @@ class Ecm {
// fetch keys from server.
virtual Status Initialize(
const EcmInitParameters& ecm_init_parameters,
const std::vector<InjectedEntitlementKeyInfo>& injected_entitlements);
// Parse a CasEncryptionResponse message holding the entitlement keys for
// generating the ECM stream. The entitlement keys are used to encrypt the
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
// Args:
// |response| a serialized CasEncryptionRequest message from the server
// holding entitlement key information (or error information).
virtual Status ProcessCasEncryptionResponse(const std::string& response);
const std::vector<EntitlementKeyInfo>& injected_entitlements);
// Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes).
@@ -153,6 +115,28 @@ class Ecm {
const std::string& track_type,
std::string* serialized_ecm) const;
// Generate a TS packet with the given |ecm| as payload.
//
// Args (all pointer parameters must be not nullptr):
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
// - |pid| program ID for the ECM stream
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to signal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |continuity_counter| continuity_counter for the ECM packet,
// it will be incremented, only last 4 bits are used
// - |packet| a buffer of size at least 188 bytes to be used to return
// the generated TS packet
//
// Returns:
// - A status indicating whether there was any error during processing
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet);
protected: // For unit tests.
// Take the input entitled |keys| and our current state, and generate
// the ECM representing the keys.
@@ -173,7 +157,7 @@ class Ecm {
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
// is added to the ECM. |track_type| allows the entitlement key to be
// associated with the original request. See Initialize() for details.
struct EntitlementKeyInfo {
struct EntitlementKeyIdValue {
std::string key_id;
std::string key_value;
};
@@ -195,9 +179,9 @@ class Ecm {
// Add an entitlement key to our current state. Even key is placed first.
void PushEntitlementKey(const std::string& track_type, bool is_even_key,
const EntitlementKeyInfo& key) {
auto emplaced =
entitlement_keys_.emplace(track_type, std::list<EntitlementKeyInfo>{});
const EntitlementKeyIdValue& key) {
auto emplaced = entitlement_keys_.emplace(
track_type, std::list<EntitlementKeyIdValue>{});
if (is_even_key) {
emplaced.first->second.push_front(key);
} else {
@@ -227,11 +211,6 @@ class Ecm {
size_t key_value_size) const;
Status ValidateIv(const std::string& iv, size_t size) const;
// TODO(user): need unit tests for CreateEntitlementRequest.
virtual Status CreateEntitlementRequest(std::string* request_string) const;
// TODO(user): need unit tests for ParseEntitlementResponse.
virtual Status ParseEntitlementResponse(const std::string& response_string);
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
virtual uint8_t age_restriction() const { return age_restriction_; }
virtual bool paired_keys_required() const { return paired_keys_required_; }
@@ -245,15 +224,14 @@ class Ecm {
std::string content_provider_;
// Content IV size may be 8 or 16. Size is set once during Initialize().
size_t content_iv_size_ = 8;
// The set of tracks that are being encrypted and require ECM streams.
std::vector<std::string> track_types_;
// Remember if a pair of keys is required (for key rotation).
bool paired_keys_required_ = false;
CryptoMode crypto_mode_ = CryptoMode::kAesCtr;
uint8_t age_restriction_ = 0;
// Entitlement keys needed for ECM generation.
// The keys are added when the CasEncryptionResponse is processed.
std::map<std::string, std::list<EntitlementKeyInfo>> entitlement_keys_;
// Maps from track_type to one/two EntitlementKeyIdValue with even key first.
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_;
};
} // namespace cas

View File

@@ -8,10 +8,13 @@
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "common/aes_cbc_util.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "protos/public/media_cas_encryption.pb.h"
namespace widevine {
@@ -50,10 +53,21 @@ constexpr char kFakeCasEncryptionResponseKeyData[] =
constexpr char kTrackType[] = "SD";
Status HandleCasEncryptionRequest(const std::string& request_string,
std::string* signed_response_string) {
Status HandleCasEncryptionRequest(const std::string& signed_request_json,
std::string* http_response_json) {
SignedCasEncryptionRequest signed_request;
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
.ok()) {
LOG(ERROR) << "Unable to parse signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionRequest request;
request.ParseFromString(request_string);
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
.ok()) {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
@@ -81,10 +95,20 @@ Status HandleCasEncryptionRequest(const std::string& request_string,
}
}
std::string response_string;
response.SerializeToString(&response_string);
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
return Status(error::INTERNAL);
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
signed_response.SerializeToString(signed_response_string);
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
.ok()) {
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
return Status(error::INTERNAL);
}
return OkStatus();
}
@@ -123,16 +147,25 @@ class EcmGeneratorTest : public testing::Test {
// Call this to setup the guts (Ecm) of the ECM Generator.
void PrepareEcmGenerator(bool key_rotation_enabled) {
ecm_ = absl::make_unique<Ecm>();
std::string entitlement_request;
std::string entitlement_response;
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
ecm_init_params_.track_types.push_back(kTrackType);
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
&entitlement_request));
FixedKeyFetcher key_fetcher;
EntitlementRequestParams request_params;
request_params.content_id = kContentId;
request_params.content_provider = kProvider;
request_params.track_types = {kTrackType};
request_params.key_rotation = key_rotation_enabled;
ASSERT_OK(key_fetcher.CreateEntitlementRequest(request_params,
&entitlement_request));
ASSERT_OK(
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
ASSERT_OK(ecm_->ProcessCasEncryptionResponse(entitlement_response));
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response,
&entitlements));
ecm_ = absl::make_unique<Ecm>();
ASSERT_OK(ecm_->Initialize(ecm_init_params_, entitlements));
ecm_gen_.set_ecm(std::move(ecm_));
}
@@ -144,10 +177,8 @@ class EcmGeneratorTest : public testing::Test {
kEcmKeyDataEven,
kEcmWrappedKeyIvEven,
{kEcmContentIvEven}};
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
kEcmKeyDataOdd,
kEcmWrappedKeyIvOdd,
{kEcmContentIvOdd}};
const KeyParameters kKeyParamsOdd{
kEcmKeyIdOdd, kEcmKeyDataOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}};
std::unique_ptr<Ecm> ecm_;
EcmInitParameters ecm_init_params_;

View File

@@ -10,7 +10,7 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "protos/public/media_cas_encryption.pb.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::Return;
@@ -37,6 +37,45 @@ constexpr size_t kEcmIvSize8 = 8;
constexpr size_t kEcmKeyInfoSize =
kEcmKeyIdSize + kEcmKeyIdSize + kEcmKeyDataSize;
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
size_t IvExpectedSize(EcmIvSize iv_size) {
return (iv_size == kIvSize8) ? kEcmIvSize8 : kEcmIvSize16;
}
@@ -103,82 +142,23 @@ class EcmTest : public testing::Test {
params_one_key_.key_rotation_enabled = false;
params_two_keys_.key_rotation_enabled = true;
params_simple_.track_types.push_back(kTrackTypeSD);
injected_entitlement_one_ = {kTrackTypeSD, true, "entitlement_id_1",
"key__value.key__value.key__value"};
injected_entitlement_two_ = {kTrackTypeSD, true, "entitlement_id_2",
"key__value.key__value.key__value"};
}
virtual void InitParams(EcmInitParameters* params,
const std::string& track_type) {
params->track_types.push_back(track_type);
}
virtual void InitParams(EcmInitParameters* params,
const std::string& track_type, EcmIvSize content_iv) {
params->track_types.push_back(track_type);
virtual void InitParams(EcmInitParameters* params, EcmIvSize content_iv) {
params->content_iv_size = content_iv;
}
virtual void ServerCall(const std::string& request_string,
std::string* signed_response_string,
bool report_status_ok, bool generate_valid_ecm) {
CasEncryptionRequest request;
ASSERT_TRUE(request.ParseFromString(request_string));
CasEncryptionResponse response;
if (!report_status_ok) {
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
} else {
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_id());
for (const auto& track_type : request.track_types()) {
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
// Add the Odd key.
key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
if (!generate_valid_ecm) {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
key->set_track_type(track_type);
}
} else {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
if (!generate_valid_ecm) {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
key->set_track_type(track_type);
}
}
}
}
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
ASSERT_TRUE(signed_response.SerializeToString(signed_response_string));
}
const std::string provider_ = "provider";
const std::string content_id_ = "content-id";
EcmInitParameters params_one_key_;
EcmInitParameters params_two_keys_;
EcmInitParameters params_simple_;
EcmInitParameters params_default_;
EntitlementKeyInfo injected_entitlement_one_;
EntitlementKeyInfo injected_entitlement_two_;
EntitledKeyInfo valid1_iv_16_8_;
EntitledKeyInfo valid2_iv_16_8_;
EntitledKeyInfo valid3_iv_16_16_;
@@ -245,152 +225,80 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code());
}
TEST_F(EcmTest, SetResponseNotInitialized) {
TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) {
Ecm ecm_gen;
const std::string response("anything");
EXPECT_EQ(error::INTERNAL,
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
InitParams(&params_one_key_, kIvSize16);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
}
TEST_F(EcmTest, InitNoTracksFail) {
TEST_F(EcmTest, InitSucceedInjectedEntitlementDouble) {
Ecm ecm_gen;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, provider_, params_default_, &request)
.error_code());
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
}
TEST_F(EcmTest, InitSucceed) {
TEST_F(EcmTest, InitFailEmptyInjectedEntitlement) {
Ecm ecm_gen;
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
ASSERT_EQ(error::NOT_FOUND,
ecm_gen.Initialize(params_simple_, {}).error_code());
}
TEST_F(EcmTest, InitFailNotEnoughInjectedEntitlement) {
Ecm ecm_gen;
ASSERT_EQ(error::INVALID_ARGUMENT,
ecm_gen.Initialize(params_simple_, {injected_entitlement_one_})
.error_code());
}
TEST_F(EcmTest, SecondInitFail) {
Ecm ecm_gen;
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
ASSERT_OK(ecm_gen.Initialize(
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
EXPECT_EQ(error::INTERNAL,
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request)
ecm_gen
.Initialize(params_simple_, {injected_entitlement_one_,
injected_entitlement_two_})
.error_code());
}
TEST_F(EcmTest, InitEmptyContentIdFail) {
Ecm ecm_gen;
const std::string empty_content_id;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(empty_content_id, provider_, params_simple_, &request)
.error_code());
}
TEST_F(EcmTest, InitEmptyProviderFail) {
Ecm ecm_gen;
const std::string empty_provider;
std::string request;
EXPECT_EQ(
error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, empty_provider, params_simple_, &request)
.error_code());
}
TEST_F(EcmTest, InitIvSize16x16OK) {
Ecm ecm_gen;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize16);
InitParams(&params_one_key_, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
}
TEST_F(EcmTest, GenerateWithNoEntitlementOneKeyFail) {
Ecm ecm_gen;
EntitledKeyInfo key1;
InitParams(&params_one_key_, kTrackTypeSD);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string ecm;
// This will fail because the entitlement key has not been acquired
// (via server call and ProcessCasEncryptionResponse()).
EXPECT_EQ(
error::INTERNAL,
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
}
TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) {
Ecm ecm_gen;
EntitledKeyInfo key1;
EntitledKeyInfo key2;
InitParams(&params_two_keys_, kTrackTypeSD);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string ecm;
// This will fail because the entitlement keys have not been acquired
// (via server call and ProcessCasEncryptionResponse()).
EXPECT_EQ(error::INTERNAL,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
}
TEST_F(EcmTest, RequestNullFail) {
Ecm ecm_gen;
EXPECT_EQ(error::INVALID_ARGUMENT,
ecm_gen.Initialize(content_id_, provider_, params_simple_, nullptr)
.error_code());
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
}
TEST_F(EcmTest, GenerateOneKeyOK) {
Ecm ecm_gen;
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
}
TEST_F(EcmTest, BadResponseFail) {
TEST_F(EcmTest, GenerateWithBadTrackType) {
Ecm ecm_gen;
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, false, true);
EntitledKeyInfo key2 = valid2_iv_16_8_;
std::string ecm;
EXPECT_EQ(error::INTERNAL,
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code());
}
TEST_F(EcmTest, GenerateTwoKeysOK) {
Ecm ecm_gen;
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
}
@@ -398,15 +306,8 @@ TEST_F(EcmTest, GenerateTwoKeysOK) {
TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
Ecm ecm_gen;
EntitledKeyInfo key1 = invalid1_key_id_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
std::string ecm;
EXPECT_EQ(
@@ -417,15 +318,8 @@ TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
TEST_F(EcmTest, GenerateOneKeyWrong) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid1_iv_16_8_;
InitParams(&params_one_key_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_one_key_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
@@ -439,16 +333,10 @@ TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
// IV size mismatch.
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
InitParams(&params_two_keys_, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
EXPECT_EQ(error::INVALID_ARGUMENT,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
@@ -458,15 +346,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize8);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_two_keys_, kIvSize8);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
@@ -479,16 +361,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid3_iv_16_16_;
EntitledKeyInfo key2 = valid4_iv_16_16_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, true);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
InitParams(&params_two_keys_, kIvSize16);
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
injected_entitlement_two_}));
std::string ecm;
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
@@ -497,26 +372,6 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
EXPECT_THAT(165, ecm.size());
}
TEST_F(EcmTest, GenerateThreeKeysIvSize16x16Fail) {
Ecm ecm_gen;
EntitledKeyInfo key1 = valid3_iv_16_16_;
EntitledKeyInfo key2 = valid4_iv_16_16_;
InitParams(&params_two_keys_, kTrackTypeSD, kIvSize16);
std::string request;
ASSERT_OK(
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
std::string response;
ServerCall(request, &response, true, false);
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
std::string ecm;
EXPECT_EQ(error::INTERNAL,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
}
// TODO(user): Add more unit tests for error paths around SerializeEcm.
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
MockEcm ecm_gen;
@@ -615,5 +470,30 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) {
kIvSize16);
}
class EcmTsPacketTest : public ::testing::Test {};
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc, packet));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId81) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc, packet));
char expected_ecm[188];
memcpy(expected_ecm, kExpectedEcmPacket, 188);
expected_ecm[5] = '\x81';
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
} // namespace cas
} // namespace widevine

View File

@@ -16,13 +16,10 @@
#include "common/crypto_util.h"
#include "common/random_util.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#include "media_cas_packager_sdk/internal/util.h"
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
// CA System ID for Widevine.
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
@@ -248,6 +245,21 @@ Status HandleParameters(const char* const request, size_t request_length,
}
offset += param_length;
break;
case ERROR_STATUS:
if (param_length != ERROR_STATUS_SIZE) {
return Status(error::INVALID_ARGUMENT,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
params->error_status.emplace_back();
BigEndianToHost16(&params->error_status.back(), request + offset);
offset += param_length;
break;
case ERROR_INFORMATION:
params->error_information.push_back(
std::string(request + offset, param_length));
offset += param_length;
break;
default:
status = ProcessPrivateParameters(request, param_type, param_length,
&offset, params);
@@ -453,39 +465,37 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
return;
}
switch (request_type) {
case ECMG_CHANNEL_SETUP: {
case ECMG_CHANNEL_SETUP:
HandleChannelSetup(params, response, response_length);
break;
}
case ECMG_CHANNEL_TEST: {
case ECMG_CHANNEL_TEST:
HandleChannelTest(params, response, response_length);
break;
}
case ECMG_CHANNEL_CLOSE: {
case ECMG_CHANNEL_CLOSE:
HandleChannelClose(params, response, response_length);
break;
}
case ECMG_STREAM_SETUP: {
case ECMG_STREAM_SETUP:
HandleStreamSetup(params, response, response_length);
break;
}
case ECMG_STREAM_TEST: {
case ECMG_STREAM_TEST:
HandleStreamTest(params, response, response_length);
break;
}
case ECMG_STREAM_CLOSE_REQUEST: {
case ECMG_STREAM_CLOSE_REQUEST:
HandleStreamCloseRequest(params, response, response_length);
break;
}
case ECMG_CW_PROVISION: {
case ECMG_CW_PROVISION:
HandleCwProvision(params, response, response_length);
break;
}
default: {
case ECMG_CHANNEL_ERROR:
HandleChannelError(params, response, response_length);
break;
case ECMG_STREAM_ERROR:
HandleStreamError(params, response, response_length);
break;
default:
BuildChannelError(params.ecm_channel_id, UNKNOWN_MESSAGE_TYPE_VALUE, "",
response, response_length);
break;
}
}
}
@@ -554,6 +564,26 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
*response_length = 0;
}
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
char* response,
size_t* response_length) const {
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
response, response_length);
return;
}
LOG(ERROR) << "Channel error received for channel id "
<< params.ecm_channel_id;
for (uint16_t error_code : params.error_status) {
LOG(ERROR) << "Error status received: " << error_code;
}
for (const auto& error_info : params.error_information) {
LOG(ERROR) << "Error info received: " << error_info;
}
*response_length = 0;
}
void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
char* response,
size_t* response_length) {
@@ -634,6 +664,33 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
response, response_length);
}
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
char* response,
size_t* response_length) const {
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
response_length);
return;
}
if (!streams_info_.contains(params.ecm_stream_id)) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_STREAM_ID_VALUE, "", response,
response_length);
return;
}
LOG(ERROR) << "Stream error received for channel id " << params.ecm_channel_id
<< ", stream id " << params.ecm_stream_id;
for (uint16_t error_code : params.error_status) {
LOG(ERROR) << "Error status received: " << error_code;
}
for (const auto& error_info : params.error_information) {
LOG(ERROR) << "Error info received: " << error_info;
}
*response_length = 0;
}
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
char* response,
size_t* response_length) {
@@ -794,6 +851,15 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
if (track_types_.empty()) {
return {error::NOT_FOUND, "Track type not specified."};
}
if (entitlement_comb_.empty()) {
return {error::NOT_FOUND, "Entitlement key id comb not specified."};
}
if (entitlement_comb_.size() !=
track_types_.size() * ecmg_config_->number_of_content_keys) {
return {error::NOT_FOUND,
"Number of injected entitlement keys must equal to number of "
"track types * number of content keys per ecm."};
}
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
EcmInitParameters ecm_init_params;
@@ -808,60 +874,20 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
(!content_ivs_.empty() && content_ivs_[0].size() == 16)) {
ecm_init_params.content_iv_size = kIvSize16;
}
ecm_init_params.track_types.assign(track_types_.begin(), track_types_.end());
std::vector<EntitlementKeyInfo> entitlements;
entitlements.reserve(entitlement_comb_.size());
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &entitlements.back();
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
entitlement->key_id = entitlement_comb_[i].key_id;
entitlement->key_value = entitlement_comb_[i].key_value;
}
ecm_ = absl::make_unique<Ecm>();
if (!entitlement_comb_.empty()) {
// Using injected entitlement keys.
if (entitlement_comb_.size() !=
track_types_.size() * ecmg_config_->number_of_content_keys) {
return {error::NOT_FOUND,
"Number of injected entitlement keys must equal to number of "
"track types * number of content keys per ecm."};
}
std::vector<InjectedEntitlementKeyInfo> entitlements;
entitlements.reserve(entitlement_comb_.size());
for (size_t i = 0; i < entitlement_comb_.size(); i++) {
entitlements.emplace_back();
InjectedEntitlementKeyInfo* entitlement = &entitlements.back();
entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i);
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
entitlement->key_id = entitlement_comb_[i].key_id;
entitlement->key_value = entitlement_comb_[i].key_value;
}
return ecm_->Initialize(ecm_init_params, entitlements);
}
// No injected enetitlement keys. Fetching entitlement keys from server.
if (content_id_.empty()) {
return {error::NOT_FOUND, "Content id not specified."};
}
if (content_provider_.empty()) {
return {error::NOT_FOUND, "Content provider not specified."};
}
std::string entitlement_request;
std::string entitlement_response;
Status status = ecm_->Initialize(content_id_, content_provider_,
ecm_init_params, &entitlement_request);
if (!status.ok()) {
return status;
}
if (key_fetcher_ == nullptr) {
if (ecmg_config_->use_fixed_fetcher) {
key_fetcher_ = absl::make_unique<FixedKeyFetcher>();
} else {
key_fetcher_ = absl::make_unique<WvCasKeyFetcher>();
}
}
status = key_fetcher_->RequestEntitlementKey(entitlement_request,
&entitlement_response);
if (!status.ok()) {
return status;
}
return ecm_->ProcessCasEncryptionResponse(entitlement_response);
return ecm_->Initialize(ecm_init_params, entitlements);
}
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
@@ -905,12 +931,12 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
// the PID field in TS packet header.
uint16_t pid = 0;
uint8_t continuity_counter = params.cp_number & 0xff;
WvCasEcm wv_cas_ecm;
WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket(
status = Ecm::GenerateTsPacket(
serialized_ecm, pid,
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
&continuity_counter, ecm_datagram);
if (cas_status != OK) {
if (!status.ok()) {
LOG(ERROR) << status;
return {error::INTERNAL, "GenerateTsPacket failed."};
}
return OkStatus();

View File

@@ -18,7 +18,6 @@
#include "absl/container/node_hash_map.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
@@ -33,7 +32,6 @@ struct EcmgConfig {
uint8_t access_criteria_transfer_mode;
uint8_t number_of_content_keys;
CryptoMode crypto_mode;
bool use_fixed_fetcher = false;
};
// A struct that represent a CP_CW_Combination.
@@ -58,6 +56,8 @@ struct EcmgParameters {
uint16_t ecm_id;
uint16_t nominal_cp_duration;
uint32_t super_cas_id;
std::vector<uint16_t> error_status;
std::vector<std::string> error_information;
// User defined paremeters below.
uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value.
@@ -103,12 +103,16 @@ class EcmgClientHandler {
size_t* response_length) const;
void HandleChannelClose(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleChannelError(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleStreamSetup(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleStreamTest(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleStreamCloseRequest(const EcmgParameters& params, char* response,
size_t* response_length);
void HandleStreamError(const EcmgParameters& params, char* response,
size_t* response_length) const;
void HandleCwProvision(const EcmgParameters& params, char* response,
size_t* response_length);
@@ -141,7 +145,6 @@ class EcmgClientHandler {
// Map from ECM_stream_id to EcmgStreamInfo.
absl::node_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
std::unique_ptr<KeyFetcher> key_fetcher_;
};
} // namespace cas

View File

@@ -50,8 +50,6 @@ static constexpr size_t kAgeRestriction = 3;
static constexpr char kCryptoMode[] = "AesScte";
static constexpr char kTrackTypesSD[] = "SD";
static constexpr char kTrackTypesHD[] = "HD";
static constexpr char kContentId[] = "CasTsFake";
static constexpr char kContentProvider[] = "widevine_test";
class EcmgClientHandlerTest : public ::testing::Test {
protected:
@@ -62,7 +60,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
config_.max_comp_time = 100;
config_.access_criteria_transfer_mode = 1;
config_.number_of_content_keys = 2;
config_.use_fixed_fetcher = true;
handler_ = absl::make_unique<EcmgClientHandler>(&config_);
}
@@ -97,8 +94,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
uint8_t age_restriction,
const std::string& crypto_mode,
const std::vector<std::string>& track_types,
const std::string& content_id,
const std::string& content_provider,
const std::vector<std::string>& entitlements,
char* message, size_t* message_length) {
EXPECT_TRUE(message != nullptr);
@@ -122,15 +117,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
track_type.size(), message, message_length);
}
}
if (!content_id.empty()) {
AddParam(CONTENT_ID, reinterpret_cast<const uint8_t*>(content_id.c_str()),
content_id.size(), message, message_length);
}
if (!content_provider.empty()) {
AddParam(CONTENT_PROVIDER,
reinterpret_cast<const uint8_t*>(content_provider.c_str()),
content_provider.size(), message, message_length);
}
if (!entitlements.empty()) {
for (const auto& entitlement : entitlements) {
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
@@ -306,36 +292,10 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceWithPrivateParameters) {
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, SuccessSequenceGeneratedRequests) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
kContentId, kContentProvider, /*entitlements*/ {},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
}
TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
@@ -361,6 +321,18 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
}
TEST_F(EcmgClientHandlerTest, SuccessChannelError) {
SetupValidChannel();
handler_->HandleRequest(kTestEcmgChannelError, response_, &response_len_);
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, SuccessStreamError) {
SetupValidChannelStream();
handler_->HandleRequest(kTestEcmgStreamError, response_, &response_len_);
EXPECT_EQ(0, response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) {
// ChannelTest without a valid channel (error).
handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_);
@@ -438,10 +410,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
// Setup a channel with an unexpected super cas id (expecting kSuperCasId).
BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD}, kContentId,
kContentProvider, /*entitlements*/ {}, request_,
&request_len_);
BuildChannelSetupRequest(
kChannelId, 0, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckChannelError(UNKNOWN_SUPER_CAS_ID_VALUE, response_, response_len_);
}
@@ -457,10 +431,12 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
}
TEST_F(EcmgClientHandlerTest, WrongParameterCryptoMode) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
"someCryptoMode", {kTrackTypesHD, kTrackTypesSD},
kContentId, kContentProvider, /*entitlements*/ {},
request_, &request_len_);
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, "someCryptoMode",
{kTrackTypesHD, kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckChannelError(INVALID_VALUE_FOR_DVB_PARAMETER, response_, response_len_);
}
@@ -473,10 +449,9 @@ TEST_F(EcmgClientHandlerTest, WrongParameterLengthSuperCasId) {
response_len_);
}
TEST_F(EcmgClientHandlerTest, NoEntitlementsNoContentIdProvider) {
TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
kCryptoMode, {kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
kCryptoMode, {kTrackTypesSD},
/*entitlements*/ {}, request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
@@ -501,7 +476,6 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode,
{kTrackTypesHD, kTrackTypesSD},
/*ContentId*/ "", /*ContentProvider*/ "",
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
request_, &request_len_);
@@ -524,6 +498,32 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
BuildChannelSetupRequest(
kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode, {kTrackTypesSD},
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
kTrackTypesSD, {kContentKeyEven, kContentKeyEven},
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
request_, &request_len_);
handler_->HandleRequest(request_, response_, &response_len_);
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
}
TEST_F(EcmgClientHandlerTest, WrongMessageLength) {
// Setup a channel with a wrong message length specified (too large).
handler_->HandleRequest(kTestEcmgChannelSetupWrongMessageLength, response_,

View File

@@ -105,5 +105,6 @@
#define CP_DURATION_SIZE (2)
#define CP_SIZE (2)
#define AGE_RESTRICTION_SIZE (1)
#define ERROR_STATUS_SIZE (2)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_

View File

@@ -16,6 +16,8 @@
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "media_cas_packager_sdk/internal/emmg_constants.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
@@ -24,6 +26,9 @@
#include "media_cas_packager_sdk/internal/util.h"
#include "protos/public/media_cas.pb.h"
// Minimum sending interval in milliseconds.
static constexpr uint16_t KMinSendIntervalMs = 2;
namespace widevine {
namespace cas {
@@ -61,7 +66,7 @@ std::string MessageTypeToString(uint16_t message_type) {
case EMMG_STREAM_BW_REQUEST:
return "EMMG_STREAM_BW_REQUEST";
case EMMG_STREAM_BW_ALLOCATION:
return "";
return "EMMG_STREAM_BW_ALLOCATION";
case EMMG_DATA_PROVISION:
return "EMMG_DATA_PROVISION";
default:
@@ -69,6 +74,68 @@ std::string MessageTypeToString(uint16_t message_type) {
}
}
Status HandleParameters(const char* const response, size_t response_length,
EmmgParameters* params) {
DCHECK(response);
DCHECK(params);
Status status;
uint16_t param_type;
uint16_t param_length;
// 'offset' is used to track where we are within |request|.
size_t offset = 0;
while (offset < response_length) {
BigEndianToHost16(&param_type, response + offset);
offset += PARAMETER_TYPE_SIZE;
BigEndianToHost16(&param_length, response + offset);
offset += PARAMETER_LENGTH_SIZE;
switch (param_type) {
case EMMG_CLIENT_ID:
if (param_length != EMMG_CLIENT_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost32(&params->client_id, response + offset);
offset += param_length;
break;
case EMMG_DATA_CHANNEL_ID:
if (param_length != EMMG_DATA_CHANNEL_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->data_channel_id, response + offset);
offset += param_length;
break;
case EMMG_DATA_STREAM_ID:
if (param_length != EMMG_DATA_STREAM_ID_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->data_stream_id, response + offset);
offset += param_length;
break;
case EMMG_BANDWIDTH:
if (param_length != EMMG_BANDWIDTH_SIZE) {
return Status(error::FAILED_PRECONDITION,
absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type));
}
BigEndianToHost16(&params->bandwidth, response + offset);
offset += param_length;
break;
default:
return Status(
error::UNIMPLEMENTED,
absl::StrCat("No implementation yet to process parameter of type ",
param_type));
break;
}
}
return OkStatus();
}
} // namespace
Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
@@ -80,9 +147,16 @@ Emmg::Emmg(EmmgConfig* emmg_config, int server_socket_fd) {
void Emmg::Start() {
SendChannelSetup();
SendStreamSetup();
// TODO(user): Send stream_BW_request message.
// TODO(user): Send stream_BW_allocation message.
SendDataProvision();
UpdateSendInterval(emmg_config_->bandwidth);
SendStreamBwRequest();
for (size_t i = 0; i < emmg_config_->max_num_message; i++) {
SendDataProvision();
absl::SleepFor(
absl::Milliseconds(std::max(KMinSendIntervalMs, send_interval_ms_)));
}
SendStreamCloseRequest();
SendChannelClose();
}
@@ -126,6 +200,26 @@ void Emmg::BuildStreamSetup() {
Host16ToBigEndian(request_ + 3, &total_param_length);
}
void Emmg::BuildStreamBwRequest() {
bzero(request_, BUFFER_SIZE);
request_length_ = 0;
simulcrypt_util::BuildMessageHeader(EMMG_MUX_PROTOCOL_VERSION,
EMMG_STREAM_BW_REQUEST, request_,
&request_length_);
simulcrypt_util::AddUint32Param(EMMG_CLIENT_ID, emmg_config_->client_id,
request_, &request_length_);
simulcrypt_util::AddUint16Param(EMMG_DATA_CHANNEL_ID,
emmg_config_->data_channel_id, request_,
&request_length_);
simulcrypt_util::AddUint16Param(EMMG_DATA_STREAM_ID,
emmg_config_->data_stream_id, request_,
&request_length_);
simulcrypt_util::AddUint16Param(EMMG_BANDWIDTH, emmg_config_->bandwidth,
request_, &request_length_);
uint16_t total_param_length = request_length_ - 5;
Host16ToBigEndian(request_ + 3, &total_param_length);
}
Status Emmg::GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer) {
DCHECK(buffer);
@@ -228,6 +322,13 @@ void Emmg::SendStreamSetup() {
ReceiveResponseAndVerify(EMMG_STREAM_STATUS);
}
void Emmg::SendStreamBwRequest() {
BuildStreamBwRequest();
Send(EMMG_STREAM_BW_REQUEST);
ReceiveResponseAndVerify(EMMG_STREAM_BW_ALLOCATION);
HandleResponse(response_);
}
void Emmg::SendDataProvision() {
BuildDataProvision();
Send(EMMG_DATA_PROVISION);
@@ -246,6 +347,52 @@ void Emmg::SendChannelClose() {
// No response for Channel_close message.
}
void Emmg::HandleResponse(const char* const response) {
DCHECK(response);
uint8_t protocol_version;
uint16_t response_type;
uint16_t response_length;
// 'offset' is used to track where we are within |request|.
size_t offset = 0;
memcpy(&protocol_version, response, PROTOCOL_VERSION_SIZE);
if (protocol_version != EMMG_MUX_PROTOCOL_VERSION) {
LOG(ERROR) << "Unexpected protocol version.";
return;
}
offset += PROTOCOL_VERSION_SIZE;
BigEndianToHost16(&response_type, response + offset);
offset += MESSAGE_TYPE_SIZE;
BigEndianToHost16(&response_length, response + offset);
offset += MESSAGE_LENGTH_SIZE;
EmmgParameters params;
Status status = HandleParameters(response + offset, response_length, &params);
if (!status.ok()) {
LOG(ERROR) << status.ToString();
return;
}
switch (response_type) {
case EMMG_STREAM_BW_ALLOCATION:
HandleStreamBwAllocation(params);
break;
default:
break;
}
}
void Emmg::UpdateSendInterval(uint16_t bandwidth_kbps) {
if (bandwidth_kbps == 0) {
return;
}
send_interval_ms_ =
std::max(KMinSendIntervalMs,
static_cast<uint16_t>((kTsPacketSize * 8) / bandwidth_kbps));
LOG(INFO) << "Send interval set to " << send_interval_ms_ << " ms.";
}
void Emmg::HandleStreamBwAllocation(const EmmgParameters& params) {
UpdateSendInterval(params.bandwidth);
}
void Emmg::ReceiveResponseAndVerify(uint16_t expected_type) {
// Read response from socket.
bzero(response_, BUFFER_SIZE);

View File

@@ -30,6 +30,16 @@ struct EmmgConfig {
uint8_t data_type;
std::string content_provider;
std::string content_id;
uint16_t bandwidth;
uint32_t max_num_message;
};
// A struct that is used to hold all possible params for a EMMG request.
struct EmmgParameters {
uint32_t client_id;
uint16_t data_channel_id;
uint16_t data_stream_id;
uint16_t bandwidth;
};
// A class that sends EMMG/PDG message to the MUX server.
@@ -46,20 +56,32 @@ class Emmg {
// Protected visibility for unit testing.
void BuildChannelSetup();
void BuildStreamSetup();
void BuildStreamBwRequest();
void BuildDataProvision();
void BuildStreamCloseRequest();
void BuildChannelClose();
void HandleResponse(const char* const response);
size_t request_length_ = 0;
char request_[BUFFER_SIZE];
// Sending interval in milliseconds. Stream specific parameter but currently
// only one stream will be setup.
uint16_t send_interval_ms_ = 0;
private:
void SendChannelSetup();
void SendStreamSetup();
void SendStreamBwRequest();
void SendDataProvision();
void SendStreamCloseRequest();
void SendChannelClose();
void HandleStreamBwAllocation(const EmmgParameters& params);
void UpdateSendInterval(uint16_t bandwidth_kbps);
Status GeneratePrivateData(const std::string& content_provider,
const std::string& content_id, uint8_t* buffer);
void ReceiveResponseAndVerify(uint16_t expected_type);

View File

@@ -65,4 +65,10 @@
#define UNKNOWN_ERROR (0x7000)
#define UNRECOVERABLE_ERROR (0x7001)
// Size (in # of bytes) of various fields.
#define EMMG_CLIENT_ID_SIZE (4)
#define EMMG_DATA_CHANNEL_ID_SIZE (2)
#define EMMG_DATA_STREAM_ID_SIZE (2)
#define EMMG_BANDWIDTH_SIZE (2)
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_CONSTANTS_H_

View File

@@ -29,10 +29,15 @@ class TestableEmmg : public Emmg {
: Emmg(emmg_config, /* server_socket_fd= */ 1) {}
void PublicBuildChannelSetup() { BuildChannelSetup(); }
void PublicBuildStreamSetup() { BuildStreamSetup(); }
void PublicBuildStreamBwRequest() { BuildStreamBwRequest(); }
void PublicBuildDataProvision() { BuildDataProvision(); }
void PublicBuildStreamCloseRequest() { BuildStreamCloseRequest(); }
void PublicBuildChannelClose() { BuildChannelClose(); }
void PublicHandleResponse(const char* const response) {
HandleResponse(response);
}
char* GetRequest() { return request_; }
uint16_t GetSendInterval() { return send_interval_ms_; }
};
class EmmgTest : public ::testing::Test {
@@ -46,6 +51,8 @@ class EmmgTest : public ::testing::Test {
config_.data_type = 0x01;
config_.content_provider = "widevine_test";
config_.content_id = "CasTsFake";
config_.bandwidth = 100;
config_.max_num_message = 100;
emmg_ = absl::make_unique<TestableEmmg>(&config_);
}
@@ -79,6 +86,17 @@ TEST_F(EmmgTest, BuildStreamSetup) {
sizeof(kTestEmmgStreamSetup)));
}
TEST_F(EmmgTest, BuildStreamBwRequest) {
emmg_->PublicBuildStreamBwRequest();
EXPECT_EQ(0, memcmp(kTestEmmgStreamBwRequest, emmg_->GetRequest(),
sizeof(kTestEmmgStreamBwRequest)));
}
TEST_F(EmmgTest, HandleStreamBwAllocation) {
emmg_->PublicHandleResponse(kTestEmmgStreamBwAllocation);
EXPECT_EQ(30, emmg_->GetSendInterval());
}
TEST_F(EmmgTest, BuildDataProvision) {
emmg_->PublicBuildDataProvision();
EXPECT_EQ(0, memcmp(kTestEmmgDataProvision, emmg_->GetRequest(),

View File

@@ -8,16 +8,31 @@
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "protos/public/media_cas_encryption.pb.h"
namespace widevine {
namespace cas {
Status FixedKeyFetcher::RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const {
Status FixedKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const {
Status status;
SignedCasEncryptionRequest signed_request;
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
.ok()) {
status = {error::INTERNAL,
"Unable to convert to SignedCasEncryptionRequest."};
LOG(ERROR) << status;
return status;
}
CasEncryptionRequest request;
request.ParseFromString(request_string);
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
.ok()) {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionResponse response;
response.set_status(CasEncryptionResponse::OK);
@@ -45,10 +60,20 @@ Status FixedKeyFetcher::RequestEntitlementKey(
}
}
std::string response_string;
response.SerializeToString(&response_string);
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
return Status(error::INTERNAL);
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
signed_response.SerializeToString(signed_response_string);
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
.ok()) {
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
return Status(error::INTERNAL);
}
return OkStatus();
}

View File

@@ -12,20 +12,26 @@
#include <string>
#include "common/status.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
namespace widevine {
namespace cas {
const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
// Perform the same role as a WV CAS KeyFetcher, but return a
// locally-constructed response that has known (predefined) entitlement keys.
class FixedKeyFetcher : public KeyFetcher {
class FixedKeyFetcher : public WvCasKeyFetcher {
public:
// Key IDs are 16 bytes, keys are 32 bytes.
// TODO(user): There should be a single entitlement key for both even
// and odd keys. Shouldn't have two different types of entitlement keys.
FixedKeyFetcher()
: even_entitlement_key_id_("fake_key_id1...."),
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
even_entitlement_key_id_("fake_key_id1...."),
even_entitlement_key_("fakefakefakefakefakefakefake1..."),
odd_entitlement_key_id_("fake_key_id2...."),
odd_entitlement_key_("fakefakefakefakefakefakefake2...") {}
@@ -35,7 +41,8 @@ class FixedKeyFetcher : public KeyFetcher {
const std::string& even_entitlement_key,
const std::string& odd_entitlement_key_id,
const std::string& odd_entitlement_key)
: even_entitlement_key_id_(even_entitlement_key_id),
: WvCasKeyFetcher(kSigningProvider, kSingingKey, kSingingIv),
even_entitlement_key_id_(even_entitlement_key_id),
even_entitlement_key_(even_entitlement_key),
odd_entitlement_key_id_(odd_entitlement_key_id),
odd_entitlement_key_(odd_entitlement_key) {}
@@ -43,18 +50,8 @@ class FixedKeyFetcher : public KeyFetcher {
FixedKeyFetcher& operator=(const FixedKeyFetcher&) = delete;
~FixedKeyFetcher() override = default;
// Get entitlement keys. Process a CasEncryptionRequest message to
// determine the keys that are needed, generate a fixed set of keys,
// and package them into a SignedCasEncryptionResponse message.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// WvCasEcm::ProcessCasEncryptionResponse().
Status RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const override;
Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const override;
private:
std::string even_entitlement_key_id_;

View File

@@ -1,44 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_
#include <string>
#include "common/status.h"
namespace widevine {
namespace cas {
// Interface for fetching various types of keys.
class KeyFetcher {
public:
KeyFetcher() = default;
KeyFetcher(const KeyFetcher&) = delete;
KeyFetcher& operator=(const KeyFetcher&) = delete;
virtual ~KeyFetcher() = default;
// Get entitlement keys. Process a CasEncryptionRequest message to
// determine the keys that are needed, generate a fixed set of keys,
// and package them into a SignedCasEncryptionResponse message.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// WvCasEcm::ProcessCasEncryptionResponse().
virtual Status RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const = 0;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_KEY_FETCHER_H_

View File

@@ -22,7 +22,7 @@ filegroup(
name = "binary_release_files",
srcs = glob(["*.h"]) + [
":wv_ecmg",
"wv_cas_key_fetcher.cc",
"wv_cas_curl_key_fetcher.cc",
"wv_cas_types.cc",
],
)
@@ -33,6 +33,7 @@ cc_binary(
deps = [
":wv_cas_ca_descriptor",
":wv_cas_ecm",
":wv_cas_key_fetcher",
":wv_cas_types",
],
)
@@ -46,7 +47,6 @@ cc_library(
"@abseil_repo//absl/base:core_headers",
"//common:status",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_cc_proto",
"//protos/public:media_cas_encryption_cc_proto",
],
@@ -89,16 +89,9 @@ cc_library(
deps = [
":wv_cas_types",
"//base",
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
"@abseil_repo//absl/memory", # buildcleaner: keep
"@abseil_repo//absl/strings", # buildcleaner: keep
"//common:crypto_util",
"@abseil_repo//absl/memory",
"//common:status",
"//example:constants",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
"//media_cas_packager_sdk/internal:mpeg2ts",
"//media_cas_packager_sdk/internal:util",
],
# Make sure libmedia_cas_packager_sdk links in symbols defined in this
# target.
@@ -111,9 +104,13 @@ cc_test(
srcs = ["wv_cas_ecm_test.cc"],
deps = [
":wv_cas_ecm",
":wv_cas_types",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"//media_cas_packager_sdk/internal:mpeg2ts",
"//common:crypto_util",
"//common:status",
"//example:constants",
"//media_cas_packager_sdk/internal:ecm",
],
)
@@ -126,14 +123,12 @@ cc_library(
"wv_cas_key_fetcher.h",
],
deps = [
":wv_cas_types",
"//base",
"//external:protobuf",
"@abseil_repo//absl/flags:flag",
"@abseil_repo//absl/strings",
"@curl_repo//:curl",
"//common:signature_util",
"//common:status",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_cc_proto",
],
)
@@ -149,13 +144,23 @@ cc_test(
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/flags:flag",
"@abseil_repo//absl/strings",
"//common:status",
"//protos/public:media_cas_encryption_cc_proto",
],
)
cc_library(
name = "wv_cas_curl_key_fetcher",
srcs = ["wv_cas_curl_key_fetcher.cc"],
hdrs = ["wv_cas_curl_key_fetcher.h"],
deps = [
":wv_cas_key_fetcher",
"@abseil_repo//absl/strings",
"@curl_repo//:curl",
"//common:status",
],
)
cc_library(
name = "wv_cas_types",
srcs = ["wv_cas_types.cc"],
@@ -164,6 +169,7 @@ cc_library(
deps = [
"//base",
"//external:protobuf",
"//common:status",
"//protos/public:media_cas_encryption_cc_proto",
],
# Make sure libmedia_cas_packager_sdk links in symbols defined in this

View File

@@ -12,7 +12,6 @@
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "common/status.h"
#include "common/string_util.h"
#include "protos/public/media_cas.pb.h"
@@ -53,16 +52,15 @@ static constexpr uint32_t kMaxValidPID = 0x1FFE;
} // namespace
WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
Status WvCasCaDescriptor::GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
std::string* serialized_ca_desc) const {
if (serialized_ca_desc == nullptr) {
LOG(ERROR) << "Return CA descriptor std::string pointer is nullptr.";
return INVALID_ARGUMENT;
return {error::INVALID_ARGUMENT,
"Return CA descriptor std::string pointer is nullptr."};
}
if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) {
LOG(ERROR) << "PID value is out of the valid range.";
return INVALID_ARGUMENT;
return {error::INVALID_ARGUMENT, "PID value is out of the valid range."};
}
std::string private_data = "";
@@ -73,8 +71,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
const size_t descriptor_length =
kCaDescriptorBasePostLengthSize + private_data.size();
if (0xFF < descriptor_length) {
LOG(ERROR) << "Private data std::string is too large.";
return INVALID_ARGUMENT;
return {error::INVALID_ARGUMENT, "Private data std::string is too large."};
}
std::bitset<kNumBitsCaDescriptorTagField> tag(kCaDescriptorTag);
@@ -91,7 +88,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
reserved.to_string(), pid.to_string());
if (descriptor_bitset.size() != kCaDescriptorBaseSize * 8) {
LOG(ERROR) << "Error creating CA descriptor.";
return INTERNAL;
return Status(error::INTERNAL);
}
std::string descriptor;
Status status =
@@ -102,7 +99,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor(
*serialized_ca_desc += private_data;
}
return OK;
return OkStatus();
}
size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {

View File

@@ -14,7 +14,7 @@
#include <string>
#include <cstdint>
#include "media_cas_packager_sdk/public/wv_cas_types.h"
#include "common/status.h"
namespace widevine {
namespace cas {
@@ -55,9 +55,10 @@ class WvCasCaDescriptor {
// section (for an EMM stream) or into a TS Program Map Table section (for an
// ECM stream). The descriptor will be 6 bytes plus any bytes added as
// (user-defined) private data.
virtual WvCasStatus GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
std::string* serialized_ca_desc) const;
virtual Status GenerateCaDescriptor(uint16_t ca_pid,
const std::string& provider,
const std::string& content_id,
std::string* serialized_ca_desc) const;
// Return the base size (before private data is added) of the CA
// descriptor. The user can call this to plan the layout of the Table section

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "protos/public/media_cas.pb.h"
@@ -37,118 +38,123 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
}
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) {
EXPECT_EQ(INVALID_ARGUMENT,
ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", nullptr));
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", nullptr)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidTooLowFail) {
const uint32_t bad_pid = 0x10 - 1;
EXPECT_EQ(INVALID_ARGUMENT, ca_descriptor_.GenerateCaDescriptor(
bad_pid, "", "", &actual_ca_descriptor_));
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidMinOK) {
const uint32_t min_pid = 0x10;
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(min_pid, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
const uint32_t max_pid = 0x1FFE;
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(max_pid, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
const uint32_t bad_pid = 0x1FFF;
EXPECT_EQ(INVALID_ARGUMENT, ca_descriptor_.GenerateCaDescriptor(
bad_pid, "", "", &actual_ca_descriptor_));
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, "", "", &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidOneByte) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(255, "", "",
&actual_ca_descriptor_));
EXPECT_OK(
ca_descriptor_.GenerateCaDescriptor(255, "", "", &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x1000, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x800, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x400, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x200, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(0x100, "", "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyProviderIgnored) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "",
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyContentIdIgnored) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
&actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest, PrivateData) {
EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\xe0\x32", 6);
CaDescriptorPrivateData private_data;
private_data.set_provider(kProvider);
@@ -176,8 +182,8 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data("X");
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
@@ -186,8 +192,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
const std::string private_data_bytes("X1234abcde");
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
}
@@ -196,8 +202,8 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) {
const std::string private_data_bytes(251, 'x');
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_);
}
@@ -206,9 +212,11 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataTooManyBytesFail) {
const std::string private_data_bytes(252, 'X');
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_EQ(INVALID_ARGUMENT,
fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, &actual_ca_descriptor_));
EXPECT_EQ(error::INVALID_ARGUMENT,
fake_descriptor
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
&actual_ca_descriptor_)
.error_code());
}
} // namespace cas

View File

@@ -0,0 +1,61 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2020 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "curl/curl.h"
#include "curl/easy.h"
namespace widevine {
namespace cas {
size_t AppendToString(void* ptr, size_t size, size_t count,
std::string* output) {
const absl::string_view data(static_cast<char*>(ptr), size * count);
absl::StrAppend(output, data);
return data.size();
}
Status WvCasCurlKeyFetcher::MakeHttpRequest(
const std::string& signed_request_json,
std::string* http_response_json) const {
if (license_server_.empty()) {
return Status(error::NOT_FOUND, "license_server is empty");
}
if (http_response_json == nullptr) {
return Status(error::INVALID_ARGUMENT, "http_response_json is null");
}
CURL* curl;
CURLcode curl_code;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, license_server_.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
(int64_t)strlen(signed_request_json.c_str()));
curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK) {
return Status(error::INTERNAL,
"curl_easy_perform() failed: " +
std::string(curl_easy_strerror(curl_code)));
}
curl_easy_cleanup(curl);
} else {
return Status(error::INTERNAL, "curl_easy_init() failed");
}
return OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,39 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2020 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_
#include <string>
#include "common/status.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
namespace widevine {
namespace cas {
class WvCasCurlKeyFetcher : public WvCasKeyFetcher {
public:
WvCasCurlKeyFetcher(const std::string& license_server,
const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv)
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv),
license_server_(license_server) {}
Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const override;
private:
std::string license_server_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_

View File

@@ -8,320 +8,85 @@
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include <stddef.h>
#include <string.h>
#include <sys/types.h>
#include <memory>
#include <vector>
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "common/crypto_util.h"
#include "common/status.h"
#include "example/constants.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/util.h"
namespace widevine {
namespace cas {
namespace {
static constexpr size_t kContentKeySizeBytes = 16;
static constexpr size_t kCsaContentKeySizeBytes = 8;
static constexpr size_t kEntitlementKeyIdSizeBytes = 16;
static constexpr size_t kEntitlementKeySizeBytes = 32;
static constexpr size_t kEcmWith8BytesContentIvSizeBytes = 149;
static constexpr size_t kEcmWith16BytesContentIvSizeBytes = 149 + (8 * 2);
static constexpr size_t kSingleKeyEcmWith8BytesContentIvSizeBytes = 77;
static constexpr size_t kSingleKeyEcmWith16BytesContentIvSizeBytes =
kSingleKeyEcmWith8BytesContentIvSizeBytes + 8;
EcmInitParameters CreateEcmInitParameters(int content_iv_size,
bool key_rotation_enabled,
CryptoMode crypto_mode) {
EcmInitParameters params;
if (content_iv_size == 8) {
params.content_iv_size = kIvSize8;
} else {
params.content_iv_size = kIvSize16;
}
params.key_rotation_enabled = key_rotation_enabled;
params.crypto_mode = crypto_mode;
// Internal ECM class can hold entitlement keys for multiple tracks.
// So we need to set a default track type here to be associated with
// the entitlement keys set later.
params.track_types.push_back(kDefaultTrackTypeSd);
return params;
EntitledKeyInfo ConvertToEntitledKeyInfo(
const WvCasContentKeyInfo& content_key_info) {
EntitledKeyInfo entitled_key_info;
entitled_key_info.key_id = content_key_info.key_id;
entitled_key_info.key_value = content_key_info.key;
entitled_key_info.content_iv = content_key_info.content_iv;
entitled_key_info.wrapped_key_iv = content_key_info.wrapped_key_iv;
return entitled_key_info;
}
EcmInitParameters ConvertToEcmInitParameters(
const WvCasEcmParameters& ecm_parameters) {
EcmInitParameters init_params;
init_params.content_iv_size = ecm_parameters.content_iv_size;
init_params.key_rotation_enabled = ecm_parameters.key_rotation_enabled;
init_params.crypto_mode = ecm_parameters.crypto_mode;
init_params.age_restriction = ecm_parameters.age_restriction;
return init_params;
}
} // namespace
WvCasStatus WvCasEcm::Initialize(int content_iv_size, bool key_rotation_enabled,
CryptoMode crypto_mode) {
if (initialized_) {
LOG(ERROR) << "Cannot initialize an instance of WvCasEcm more than once";
return UNAVAILABLE;
}
if (content_iv_size != 8 && content_iv_size != 16) {
LOG(ERROR) << "Only support content_iv_size being 8 now";
return INVALID_ARGUMENT;
}
content_iv_size_ = content_iv_size;
key_rotation_enabled_ = key_rotation_enabled;
crypto_mode_ = crypto_mode;
initialized_ = true;
return OK;
WvCasEcm::WvCasEcm(
const WvCasEcmParameters& ecm_parameters,
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
CHECK(!injected_entitlements.empty());
ecm_param_ = ecm_parameters;
injected_entitlements_.assign(injected_entitlements.begin(),
injected_entitlements.end());
}
WvCasStatus WvCasEcm::GenerateEcm(
const char* const even_key, const char* const even_content_iv,
const char* const even_entitlement_key_id,
const char* const even_entitlement_key, const char* const odd_key,
const char* const odd_content_iv, const char* const odd_entitlement_key_id,
const char* const odd_entitlement_key, std::string* ecm) const {
DCHECK(even_key);
DCHECK(even_content_iv);
DCHECK(even_entitlement_key_id);
DCHECK(even_entitlement_key);
DCHECK(odd_key);
DCHECK(odd_content_iv);
DCHECK(odd_entitlement_key_id);
DCHECK(odd_entitlement_key);
DCHECK(ecm);
if (!initialized_) {
LOG(ERROR) << "WvCasEcm has not been properly initialized";
return UNAVAILABLE;
}
if (!key_rotation_enabled_) {
LOG(ERROR) << "Please call GenerateSingleKeyEcm() instead when key "
"rotation is disabled";
return UNAVAILABLE;
}
// Create strings in such way to handle possible '\x00' bytes in the input.
std::string even_key_str;
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
even_key_str = std::string(even_key, kCsaContentKeySizeBytes);
even_key_str = even_key_str + even_key_str; // Make it 16 bytes.
} else {
even_key_str = std::string(even_key, kContentKeySizeBytes);
}
std::string even_content_iv_str(even_content_iv, content_iv_size_);
std::string even_entitlement_key_id_str(even_entitlement_key_id,
kEntitlementKeyIdSizeBytes);
std::string even_entitlement_key_str(even_entitlement_key,
kEntitlementKeySizeBytes);
std::string odd_key_str;
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
odd_key_str = std::string(odd_key, kCsaContentKeySizeBytes);
odd_key_str = odd_key_str + odd_key_str; // Make it 16 bytes.
} else {
odd_key_str = std::string(odd_key, kContentKeySizeBytes);
}
std::string odd_content_iv_str(odd_content_iv, content_iv_size_);
std::string odd_entitlement_key_id_str(odd_entitlement_key_id,
kEntitlementKeyIdSizeBytes);
std::string odd_entitlement_key_str(odd_entitlement_key,
kEntitlementKeySizeBytes);
// Double check some input sizes.
if (even_key_str.size() != kContentKeySizeBytes ||
odd_key_str.size() != kContentKeySizeBytes) {
LOG(ERROR) << "Size of content key is incorrect";
return INVALID_ARGUMENT;
}
if (even_content_iv_str.size() != content_iv_size_ ||
odd_content_iv_str.size() != content_iv_size_) {
LOG(ERROR) << "Size of content IV is incorrect";
return INVALID_ARGUMENT;
}
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,
std::string* serialized_ecm) const {
// Create an instance of Ecm in order to set the entitlement keys.
auto cas_ecm = absl::make_unique<Ecm>();
std::string entitlement_request;
std::string entitlement_response;
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
content_iv_size_, key_rotation_enabled_, crypto_mode_);
// 'content_id' and 'provider' are used in entitlement key request/response
// only, NOT needed for constructing the ECM. So we just use hardcoded value
// here for now.
// TODO(user): When we want to retrieve entitlement key from License Server
// we need to figure out a way to provide real 'content_id' and 'provder'
// to this function here.
Status status;
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
ecm_init_params, &entitlement_request))
.ok()) {
LOG(ERROR) << "Failed to initialize ECM class.";
return INTERNAL;
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
if (!status.ok()) {
LOG(ERROR) << "Failed to get initialize ECM class." << status;
return status;
}
FixedKeyFetcher fixed_key_fetcher(
/* even_entitlement_key_id= */ even_entitlement_key_id_str,
/* even_entitlement_key= */ even_entitlement_key_str,
/* odd_entitlement_key_id= */ odd_entitlement_key_id_str,
/* odd_entitlement_key= */ odd_entitlement_key_str);
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
&entitlement_response))
.ok()) {
LOG(ERROR) << "Failed to get entitlement key.";
return INTERNAL;
}
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
.ok()) {
LOG(ERROR) << "Failed to process entitlement key.";
return INTERNAL;
}
// Generate ECM.
std::vector<EntitledKeyInfo> keys;
keys.reserve(2);
// Add even entitlement key.
keys.emplace_back();
keys[0].key_value = even_key_str;
keys[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
keys[0].key_id = crypto_util::DeriveKeyId(even_key_str);
keys[0].content_iv = even_content_iv_str;
// Add odd entitlement key.
keys.emplace_back();
keys[1].key_value = odd_key_str;
keys[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str);
keys[1].key_id = crypto_util::DeriveKeyId(odd_key_str);
keys[1].content_iv = odd_content_iv_str;
status = cas_ecm->GenerateEcm(&keys[0], &keys[1], kDefaultTrackTypeSd, ecm);
size_t expected_ecm_size = content_iv_size_ == 8
? kEcmWith8BytesContentIvSizeBytes
: kEcmWith16BytesContentIvSizeBytes;
if (ecm->size() != expected_ecm_size) {
LOG(ERROR) << "Generated an ECM with invalid size: " << ecm->size();
ecm->clear();
return INTERNAL;
}
return OK;
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key);
return cas_ecm->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
serialized_ecm);
}
WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
const char* const even_key, const char* const even_content_iv,
const char* const even_entitlement_key_id,
const char* const even_entitlement_key, std::string* ecm) const {
DCHECK(even_key);
DCHECK(even_content_iv);
DCHECK(even_entitlement_key_id);
DCHECK(even_entitlement_key);
DCHECK(ecm);
if (!initialized_) {
LOG(ERROR) << "WvCasEcm has not been properly initialized";
return UNAVAILABLE;
}
if (key_rotation_enabled_) {
LOG(ERROR)
<< "Please call GenerateEcm() instead when key rotation is enabled";
return UNAVAILABLE;
}
// Create strings in such way to handle possible '\x00' bytes in the input.
std::string even_key_str;
if (crypto_mode_ == CryptoMode::kDvbCsa2) {
even_key_str = std::string(even_key, kCsaContentKeySizeBytes);
even_key_str = even_key_str + even_key_str; // Make it 16 bytes.
} else {
even_key_str = std::string(even_key, kContentKeySizeBytes);
}
std::string even_content_iv_str(even_content_iv, content_iv_size_);
std::string even_entitlement_key_id_str(even_entitlement_key_id,
kEntitlementKeyIdSizeBytes);
std::string even_entitlement_key_str(even_entitlement_key,
kEntitlementKeySizeBytes);
// Double check some input sizes.
if (even_key_str.size() != kContentKeySizeBytes) {
LOG(ERROR) << "Size of content key is incorrect";
return INVALID_ARGUMENT;
}
if (even_content_iv_str.size() != content_iv_size_) {
LOG(ERROR) << "Size of content IV is incorrect";
return INVALID_ARGUMENT;
}
Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type,
std::string* serialized_ecm) const {
// Create an instance of Ecm in order to set the entitlement keys.
auto cas_ecm = absl::make_unique<Ecm>();
std::string entitlement_request;
std::string entitlement_response;
EcmInitParameters ecm_init_params = CreateEcmInitParameters(
content_iv_size_, key_rotation_enabled_, crypto_mode_);
// 'content_id' and 'provider' are used in entitlement key request/response
// only, NOT needed for constructing the ECM. So we just use hardcoded value
// here for now.
// TODO(user): When we want to retrieve entitlement key from License Server
// we need to figure out a way to provide real 'content_id' and 'provder'
// to this function here.
Status status;
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
ecm_init_params, &entitlement_request))
.ok()) {
LOG(ERROR) << "Failed to get initialize ECM class.";
return INTERNAL;
}
FixedKeyFetcher fixed_key_fetcher(
/* even_entitlement_key_id= */ even_entitlement_key_id_str,
/* even_entitlement_key= */ even_entitlement_key_str,
/* odd_entitlement_key_id= */ "",
/* odd_entitlement_key= */ "");
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
&entitlement_response))
.ok()) {
LOG(ERROR) << "Failed to get entitlement key.";
return INTERNAL;
}
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
.ok()) {
LOG(ERROR) << "Failed to process entitlement key.";
return INTERNAL;
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
if (!status.ok()) {
LOG(ERROR) << "Failed to get initialize ECM class." << status;
return status;
}
// Generate ECM.
EntitledKeyInfo key;
key.key_value = even_key_str;
key.wrapped_key_iv = crypto_util::DeriveIv(even_key_str);
key.key_id = crypto_util::DeriveKeyId(key.key_value);
key.content_iv = even_content_iv_str;
status = cas_ecm->GenerateSingleKeyEcm(&key, kDefaultTrackTypeSd, ecm);
size_t expected_ecm_size = content_iv_size_ == 8
? kSingleKeyEcmWith8BytesContentIvSizeBytes
: kSingleKeyEcmWith16BytesContentIvSizeBytes;
if (ecm->size() != expected_ecm_size) {
LOG(ERROR) << "Generated an ECM with invalid size: " << ecm->size();
ecm->clear();
return INTERNAL;
}
return OK;
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
return cas_ecm->GenerateSingleKeyEcm(&entitled_key, track_type,
serialized_ecm);
}
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet) const {
ssize_t bytes_modified = 0;
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
packet, &bytes_modified);
if (!status.ok() || bytes_modified != kTsPacketSize) {
memset(packet, 0, kTsPacketSize);
LOG(ERROR) << "Failed to generate TS packet: " << status;
return INTERNAL;
}
return OK;
Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet) {
return Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter, packet);
}
} // namespace cas

View File

@@ -10,102 +10,91 @@
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <string>
#include <vector>
#include <cstdint>
#include "common/status.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
namespace cas {
// Information needed to generate content key portion of ECM.
// Fields:
// |key_id| key ID for the content key, must be 16 bytes.
// |key| content key value (aka control word), must be 16 bytes. It will be
// wrapped (encrypted) by corresponding entitlement key (decided by track
// type), together with |wrapped_key_iv| as the initial vector.
// |content_iv| content initial vector, must be 8 or 16 bytes. It is used for
// decrypting the content stream.
// |wrapped_key_iv| must be 16 bytes. It is used to encrypt |key|.
struct WvCasContentKeyInfo {
std::string key_id;
std::string key;
std::string content_iv;
std::string wrapped_key_iv;
};
// Information needed to start a new ECM stream.
// Fields:
// |content_iv_size| size of all content key IVs in the ECM stream.
// A constant of type EcmIvSize specifying 8 or 16.
// |key_rotation_enabled| the encryption uses multiple keys in rotation.
// |crypto_mode| the encryption mode used for the content stream.
// A constant of type CryptoMode.
// |age_restriction| minimum age required; the value represents actual age.
struct WvCasEcmParameters {
EcmIvSize content_iv_size = kIvSize8;
bool key_rotation_enabled = true;
CryptoMode crypto_mode = CryptoMode::kAesCtr;
uint8_t age_restriction = 0;
};
// Class for generating Widevine CAS ECMs.
// See wv_cas_ecm_test.cc for example usage.
// This class is NOT thread-safe.
class WvCasEcm {
public:
WvCasEcm() = default;
// Construct of class WvCasEcm.
// Args:
// |ecm_init_parameters| encryption-related parameters for configuring
// the ECM stream.
// |injected_entitlement| entitlement key info. May be fetched from the server
// with WvCasKeyFetcher.
WvCasEcm(const WvCasEcmParameters& ecm_parameters,
const std::vector<EntitlementKeyInfo>& injected_entitlements);
WvCasEcm(const WvCasEcm&) = delete;
WvCasEcm& operator=(const WvCasEcm&) = delete;
virtual ~WvCasEcm() = default;
// Initialize an instance of this class.
//
// Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes).
// Args:
// - |content_iv_size| iv size in bytes for encrypting the content,
// only support 8 bytes or 16 bytes
// When using 8 bytes content_iv, we assume additional 8 bytes of '\x00' are
// appended to the iv to form a 16 bytes AES IV when content is encrypted.
// - |key_rotation_enabled| whether key rotation is enabled,
// if this is 'true' only subsequent call to GenerateEcm will be allowed,
// if this is 'false' only subsequent call to GenerateSingleKeyEcm will
// be allowed
// - |crypto_mode| crypto mode for encrypting content
//
// Returns:
// - A status indicating whether there was any error during initialization
//
// Note:
// - 'even'/'odd' key in the ECM will be be encrypted using AEC_CBC
virtual WvCasStatus Initialize(int content_iv_size, bool key_rotation_enabled,
CryptoMode crypto_mode);
// |even_key| information for even key to be encoded into ECM.
// |odd_key| information for odd key to be encoded into ECM.
// |track_type| the track that the keys are being used to encrypt.
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
// consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate
// entitlement key. Wrapping modifies the original structure.
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,
std::string* serialized_ecm) const;
// Generate an ECM containing two keys (even and odd). Can be called when
// |key_rotation_enabled| is initialized to 'true'.
//
// Args (all pointer parameters must be not nullptr):
// - |even_key| clear even content key, must be 8 bytes for kDvbCsa2,
// 16 bytes for kAesCtr or kAesCbc
// - |even_content_iv| iv used along with |even_key| for encrypting content,
// length must match |content_iv_size| set during initialization
// - |even_entitlement_key_id| key id for |even_entitlement_key|,
// must be 16 bytes length
// - |even_entitlement_key| entitlement key used to encrypt even key,
// must be 32 bytes length
// - |odd_key| clear odd content key, must be 8 bytes for kDvbCsa2,
// 16 bytes for kAesCtr or kAesCbc
// - |odd_content_iv| iv used along with |odd_key| for encrypting content,
// length must match |content_iv_size| set during initialization
// - |odd_entitlement_key_id| key id for |odd_entitlement_key|,
// must be 16 bytes length
// - |odd_entitlement_key| entitlement key used to encrypt odd key,
// must be 32 bytes length
// - |ecm| for returning the generated ECM,
// size of the generated ecm is 149 bytes when content iv is 8 bytes
// 165 bytes when content iv is 16 bytes
//
// Returns:
// - A status indicating whether there was any error during processing
virtual WvCasStatus GenerateEcm(const char* const even_key,
const char* const even_content_iv,
const char* const even_entitlement_key_id,
const char* const even_entitlement_key,
const char* const odd_key,
const char* const odd_content_iv,
const char* const odd_entitlement_key_id,
const char* const odd_entitlement_key,
std::string* ecm) const;
// Generate an ECM containing only a singe even key. Can be called when
// |key_rotation_enabled| is initialized to 'false'.
//
// Args (all pointer parameters must be not nullptr):
// - |even_key| clear even content key, must be 8 bytes for kDvbCsa2,
// 16 bytes for kAesCtr or kAesCbc
// - |even_content_iv| iv used along with |even_key| for encrypting content,
// length must match |content_iv_size| set during initialization
// - |even_entitlement_key_id| key id for |even_entitlement_key|,
// must be 16 bytes length
// - |even_entitlement_key| entitlement key used to encrypt even key,
// must be 32 bytes length
// - |ecm| for returning the generated ECM,
// size of the generated ecm is 77 bytes when content iv is 8 bytes
// 85 bytes when content iv is 16 bytes
//
// Returns:
// - A status indicating whether there was any error during processing
virtual WvCasStatus GenerateSingleKeyEcm(
const char* const even_key, const char* const even_content_iv,
const char* const even_entitlement_key_id,
const char* const even_entitlement_key, std::string* ecm) const;
// Accept a key and IV and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes). This call is specifically for the case
// where key rotation is disabled.
// Args:
// |key| information for key to be encoded into ECM.
// |track_type| the track that the key is being used to encrypt.
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings.
virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type,
std::string* serialized_ecm) const;
// Generate a TS packet with the given |ecm| as payload.
//
@@ -125,16 +114,13 @@ class WvCasEcm {
//
// Returns:
// - A status indicating whether there was any error during processing
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet) const;
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet);
private:
bool initialized_ = false;
int content_iv_size_;
bool key_rotation_enabled_;
CryptoMode crypto_mode_;
WvCasEcmParameters ecm_param_;
std::vector<EntitlementKeyInfo> injected_entitlements_;
};
} // namespace cas

View File

@@ -8,385 +8,194 @@
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include <string.h>
#include <stddef.h>
#include <tuple>
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::Test;
#include "common/crypto_util.h"
#include "common/status.h"
#include "example/constants.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
namespace cas {
namespace {
const char kEvenKey[] = "even_key........"; // 16 bytes
const char kCsaEvenKey[] = "even_key"; // 8 bytes
const char kCsaEvenKeyWithNul[] = {'\x01', '\x00', '\x00', '\x00',
'\x00', '\x00', '\x00', '\x01'};
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
const char kEvenContentIv16Bytes[] = "even_iv........."; // 16 bytes
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes
const char kEvenEntitlementKey[] =
"even_entitlement_key............"; // 32 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kCsaOddKey[] = "odd_key."; // 8 bytes
const char kCsaOddKeyWithNul[] = {'\x00', '\x02', '\x00', '\x00',
'\x00', '\x00', '\x02', '\x00'};
"even_entitlement_key............"; // 32 bytes
const char kEvenEntitlementWrappedKeyIv[] = "1234567812345678"; // 16 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
const char kOddContentIv16Bytes[] = "od_iv..........."; // 16 bytes
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
const char kOddEntitlementKey[] =
"odd_entitlement_key............."; // 32 bytes
"odd_entitlement_key............."; // 32 bytes
const char kOddEntitlementWrappedKeyIv[] = "1234567812345678"; // 16 bytes
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
static constexpr size_t kEcmWith8BytesContentIvSizeBytes = 149;
static constexpr size_t kEcmWith16BytesContentIvSizeBytes = 149 + (8 * 2);
static constexpr size_t kSingleKeyEcmWith8BytesContentIvSizeBytes = 77;
static constexpr size_t kSingleKeyEcmWith16BytesContentIvSizeBytes =
kSingleKeyEcmWith8BytesContentIvSizeBytes + 8;
} // namespace
class WvCasEcmTest : public Test {
class WvCasEcmTest
: public ::testing::Test,
public ::testing::WithParamInterface<::testing::tuple<
int /*content_iv_size*/, bool /*key_rotation_enabled*/>> {
protected:
WvCasEcmTest() {}
WvCasEcm wv_cas_ecm_;
WvCasEcmTest() {
content_iv_size_ = std::get<0>(GetParam());
key_rotation_ = std::get<1>(GetParam());
}
WvCasEcmParameters CreateEcmInitParameters(bool key_rotation,
int content_iv_size) {
WvCasEcmParameters params;
params.content_iv_size = content_iv_size == 8 ? kIvSize8 : kIvSize16;
params.key_rotation_enabled = key_rotation;
params.crypto_mode = CryptoMode::kAesScte;
params.age_restriction = 0;
return params;
}
std::vector<EntitlementKeyInfo> CreateInjectedEntitlements(
bool key_rotation) {
std::vector<EntitlementKeyInfo> injected_entitlements;
injected_entitlements.reserve(key_rotation ? 2 : 1);
injected_entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
entitlement->key_id = kEvenEntitlementKeyId;
entitlement->key_value = kEvenEntitlementKey;
entitlement->is_even_key = true;
entitlement->track_type = kDefaultTrackTypeSd;
if (key_rotation) {
injected_entitlements.emplace_back();
EntitlementKeyInfo* entitlement = &injected_entitlements.back();
entitlement->key_id = kOddEntitlementKeyId;
entitlement->key_value = kOddEntitlementKey;
entitlement->is_even_key = false;
entitlement->track_type = kDefaultTrackTypeSd;
}
return injected_entitlements;
}
std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
int content_iv_size) {
std::vector<WvCasContentKeyInfo> content_keys;
content_keys.reserve(key_rotation ? 2 : 1);
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kEvenKey;
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
content_key->content_iv =
content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes;
content_key->wrapped_key_iv = kEvenEntitlementWrappedKeyIv;
if (key_rotation) {
content_keys.emplace_back();
WvCasContentKeyInfo* content_key = &content_keys.back();
content_key->key = kOddKey;
content_key->key_id = crypto_util::DeriveKeyId(content_key->key);
content_key->content_iv =
content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes;
content_key->wrapped_key_iv = kOddEntitlementWrappedKeyIv;
}
return content_keys;
}
int content_iv_size_;
bool key_rotation_;
};
TEST_F(WvCasEcmTest, Initialize_Twice_Error) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
EXPECT_EQ(UNAVAILABLE,
wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
TEST_P(WvCasEcmTest, InitializeSuccess) {
WvCasEcmParameters params =
CreateEcmInitParameters(key_rotation_, content_iv_size_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(key_rotation_);
WvCasEcm wv_cas_ecm(params, entitlements);
}
TEST_F(WvCasEcmTest, Initialize_InvalidContentIvSize_Error) {
EXPECT_EQ(INVALID_ARGUMENT,
wv_cas_ecm_.Initialize(/* content_iv_size= */ 4,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
}
TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) {
WvCasEcmParameters params = CreateEcmInitParameters(
/*key_rotation*/ true, content_iv_size_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(/*key_rotation*/ true);
WvCasEcm wv_cas_ecm(params, entitlements);
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_KeyRotationEnabled_Error) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
std::vector<WvCasContentKeyInfo> content_keys =
CreateContentKeyInfo(/*key_rotation*/ false, content_iv_size_);
std::string actual_ecm;
EXPECT_EQ(UNAVAILABLE,
wv_cas_ecm_.GenerateSingleKeyEcm("", "", "", "", &actual_ecm));
EXPECT_EQ(error::INVALID_ARGUMENT,
wv_cas_ecm
.GenerateSingleKeyEcm(content_keys[0], kDefaultTrackTypeSd,
&actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateEcm_KeyRotationDisabled_Error) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
CryptoMode::kAesCtr));
TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) {
WvCasEcmParameters params = CreateEcmInitParameters(
/*key_rotation*/ false, content_iv_size_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(/*key_rotation*/ false);
WvCasEcm wv_cas_ecm(params, entitlements);
std::vector<WvCasContentKeyInfo> content_keys =
CreateContentKeyInfo(/*key_rotation*/ true, content_iv_size_);
std::string actual_ecm;
EXPECT_EQ(UNAVAILABLE, wv_cas_ecm_.GenerateEcm("", "", "", "", "", "", "", "",
&actual_ecm));
EXPECT_EQ(error::INVALID_ARGUMENT,
wv_cas_ecm
.GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Ctr_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
TEST_P(WvCasEcmTest, GenerateEcmSuccess) {
WvCasEcmParameters params =
CreateEcmInitParameters(key_rotation_, content_iv_size_);
std::vector<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(key_rotation_);
WvCasEcm wv_cas_ecm(params, entitlements);
std::vector<WvCasContentKeyInfo> content_keys =
CreateContentKeyInfo(key_rotation_, content_iv_size_);
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */ kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey,
/* odd_key= */ kOddKey,
/* odd_content_iv= */ kOddContentIv8Bytes,
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40203806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e34cd74b6b998889aad0e71b44bdd8c"
"0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3ebba4a0bd876f6464"
"5f69762e2e",
absl::BytesToHexString(actual_ecm));
if (key_rotation_) {
EXPECT_OK(wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, &actual_ecm));
} else {
EXPECT_OK(wv_cas_ecm.GenerateSingleKeyEcm(
content_keys[0], kDefaultTrackTypeSd, &actual_ecm));
}
EXPECT_EQ(absl::BytesToHexString(actual_ecm).substr(0, 7), "4ad4020");
if (content_iv_size_ == 8) {
if (key_rotation_) {
EXPECT_EQ(actual_ecm.size(), kEcmWith8BytesContentIvSizeBytes);
} else {
EXPECT_EQ(actual_ecm.size(), kSingleKeyEcmWith8BytesContentIvSizeBytes);
}
} else {
if (key_rotation_) {
EXPECT_EQ(actual_ecm.size(), kEcmWith16BytesContentIvSizeBytes);
} else {
EXPECT_EQ(actual_ecm.size(), kSingleKeyEcmWith16BytesContentIvSizeBytes);
}
}
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Ctr_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
CryptoMode::kAesCtr));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */ kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40202806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Ctr_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCtr));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */
kEvenContentIv16Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey,
/* odd_key= */ kOddKey,
/* odd_content_iv= */
kOddContentIv16Bytes,
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40203c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
"bba4a0bd876f645f69762e2e2e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Ctr_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
/* key_rotation_enabled= */ false,
CryptoMode::kAesCtr));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */
kEvenContentIv16Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40202c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
/* key_rotation_enabled= */ true,
CryptoMode::kAesCbc));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */
kEvenContentIv16Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey,
/* odd_key= */ kOddKey,
/* odd_content_iv= */
kOddContentIv16Bytes,
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40201c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
"bba4a0bd876f645f69762e2e2e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 16,
/* key_rotation_enabled= */ false,
CryptoMode::kAesCbc));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_content_iv= */
kEvenContentIv16Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40200c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kDvbCsa2));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateEcm(
/* even_key= */ kCsaEvenKey,
/* even_content_iv= */
kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey,
/* odd_key= */ kCsaOddKey,
/* odd_content_iv= */
kOddContentIv8Bytes,
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40205806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
"5f69762e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
CryptoMode::kDvbCsa2));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kCsaEvenKey,
/* even_content_iv= */
kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40204806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
CryptoMode::kDvbCsa2));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateEcm(
/* even_key= */ kCsaEvenKeyWithNul,
/* even_content_iv= */
kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey,
/* odd_key= */ kCsaOddKeyWithNul,
/* odd_content_iv= */
kOddContentIv8Bytes,
/* odd_entitlement_key_id= */ kOddEntitlementKeyId,
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40205806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
"5f69762e2e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest,
GenerateSingleKeyEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
EXPECT_EQ(OK, wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
CryptoMode::kDvbCsa2));
std::string actual_ecm;
EXPECT_EQ(OK,
wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kCsaEvenKeyWithNul,
/* even_content_iv= */
kEvenContentIv8Bytes,
/* even_entitlement_key_id= */ kEvenEntitlementKeyId,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40204806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId80) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc,
packet));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId81) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc,
packet));
char expected_ecm[188];
memcpy(expected_ecm, kExpectedEcmPacket, 188);
expected_ecm[5] = '\x81';
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
INSTANTIATE_TEST_SUITE_P(WvCasEcmTests, WvCasEcmTest,
::testing::Combine(::testing::Values(8, 16),
::testing::Bool()));
} // namespace cas
} // namespace widevine

View File

@@ -11,49 +11,61 @@
#include <stddef.h>
#include <string.h>
#include <cstdint>
#include <cstddef>
#include <string>
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "absl/flags/flag.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "curl/curl.h"
#include "curl/easy.h"
#include "common/signature_util.h"
#include "common/status.h"
#include "protos/public/media_cas_encryption.pb.h"
using google::protobuf::util::JsonPrintOptions;
using google::protobuf::util::JsonStringToMessage;
using google::protobuf::util::MessageToJsonString;
ABSL_FLAG(std::string, license_server, "",
"HTTP URL to the license server for making CAS encryption request.");
ABSL_FLAG(std::string, signing_provider, "",
"Name of the provider signing the CAS encryption request.");
ABSL_FLAG(std::string, signing_key, "",
"AES key (in hex) for signing the CAS encryption request.");
ABSL_FLAG(std::string, signing_iv, "",
"AES iv (in hex) for signing the CAS encryption request.");
namespace widevine {
namespace cas {
Status WvCasKeyFetcher::RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const {
if (absl::GetFlag(FLAGS_signing_provider).empty() ||
absl::GetFlag(FLAGS_signing_key).empty() ||
absl::GetFlag(FLAGS_signing_iv).empty()) {
return Status(
error::INVALID_ARGUMENT,
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
WvCasKeyFetcher::WvCasKeyFetcher(const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv) {
signing_provider_ = signing_provider;
signing_key_ = signing_key;
signing_iv_ = signing_iv;
}
Status WvCasKeyFetcher::CreateEntitlementRequest(
const EntitlementRequestParams& request_params,
std::string* signed_request_json) const {
if (request_params.content_id.empty()) {
return {error::INVALID_ARGUMENT, "Content ID is empty."};
}
if (request_params.content_provider.empty()) {
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
}
if (request_params.track_types.empty()) {
return {error::INVALID_ARGUMENT, "Track Types is empty."};
}
if (signed_request_json == nullptr) {
return {error::INVALID_ARGUMENT, "signed_request_json is null."};
}
if (signing_provider_.empty() || signing_key_.empty() ||
signing_iv_.empty()) {
return Status(error::NOT_FOUND,
"'signing_provider', 'signing_key' or 'signing_iv' is empty");
}
// Processes request.
CasEncryptionRequest request;
request.ParseFromString(request_string);
request.set_content_id(request_params.content_id);
request.set_provider(request_params.content_provider);
request.set_key_rotation(request_params.key_rotation);
// Add labels for tracks.
for (const auto& track_type : request_params.track_types) {
request.add_track_types(track_type);
}
std::string request_json;
JsonPrintOptions print_options;
// Set this option so that the json output is
@@ -74,87 +86,102 @@ Status WvCasKeyFetcher::RequestEntitlementKey(
signed_request.set_request(request_json);
std::string signature;
if (!signature_util::GenerateAesSignature(
request_json,
absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_key)),
absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_iv)), &signature)
request_json, absl::HexStringToBytes(signing_key_),
absl::HexStringToBytes(signing_iv_), &signature)
.ok()) {
return Status(error::INTERNAL, "Failed to sign the request.");
}
signed_request.set_signature(signature);
signed_request.set_signer(absl::GetFlag(FLAGS_signing_provider));
std::string signed_request_json;
signed_request.set_signer(signing_provider_);
// NOTE: MessageToJsonString will automatically converts the 'request' and
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
// are of type 'bytes'.
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
if (!MessageToJsonString(signed_request, signed_request_json).ok()) {
return Status(error::INTERNAL,
"Failed to convert signed request message to json.");
}
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
LOG(INFO) << "Json SignedCasEncryptionRequest: " << *signed_request_json;
return OkStatus();
}
// Makes HTTP request against License Server.
std::string http_response_json;
Status status = MakeHttpRequest(signed_request_json, &http_response_json);
if (!status.ok()) {
return status;
Status WvCasKeyFetcher::ParseEntitlementResponse(
const std::string& http_response_json,
std::vector<EntitlementKeyInfo>* entitlements) {
if (http_response_json.empty()) {
return {error::INVALID_ARGUMENT, "Response std::string is empty."};
}
if (entitlements == nullptr) {
return {error::INVALID_ARGUMENT, "entitlements is null."};
}
if (!entitlements->empty()) {
return {error::INVALID_ARGUMENT, "entitlements is not empty."};
}
LOG(INFO) << "Json HTTP response: " << http_response_json;
HttpResponse http_response;
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
return Status(error::INTERNAL,
"Failed to convert http response json to message.");
}
// Processes signed response.
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
CasEncryptionResponse response;
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
return Status(error::INTERNAL,
"Failed to convert response json to message.");
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response.SerializeAsString());
signed_response.SerializeToString(signed_response_string);
return OkStatus();
}
size_t AppendToString(void* ptr, size_t size, size_t count,
std::string* output) {
const absl::string_view data(static_cast<char*>(ptr), size * count);
absl::StrAppend(output, data);
return data.size();
}
Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const {
CHECK(http_response_json);
if (absl::GetFlag(FLAGS_license_server).empty()) {
return Status(error::INVALID_ARGUMENT, "Flag 'license_server' is empty");
if (response.status() != CasEncryptionResponse::OK) {
return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ",
response.status(), " : ",
response.status_message()));
}
CURL* curl;
CURLcode curl_code;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL,
absl::GetFlag(FLAGS_license_server).c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
(int64_t)strlen(signed_request_json.c_str()));
curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK) {
return Status(error::INTERNAL,
"curl_easy_perform() failed: " +
std::string(curl_easy_strerror(curl_code)));
if (response.entitlement_keys().empty()) {
return {error::INTERNAL,
"Malformed entitlement response: missing entitlement keys field."};
}
// Scan available entitlement keys for the ones that are needed.
// For non-key-rotation, this is a key with a track type and not even or odd.
// For key rotation, this is a key with a track type and even or odd.
for (const auto& key : response.entitlement_keys()) {
if (!key.has_track_type()) {
return {error::INTERNAL,
"Malformed entitlement response: missing track type field."};
}
curl_easy_cleanup(curl);
} else {
return Status(error::INTERNAL, "curl_easy_init() failed");
if (!key.has_key_slot()) {
return {error::INTERNAL,
"Malformed entitlement response: missing key slot field."};
}
entitlements->emplace_back();
EntitlementKeyInfo* entitlement = &entitlements->back();
entitlement->key_id = key.key_id();
entitlement->key_value = key.key();
entitlement->track_type = key.track_type();
// EVEN or SINGLE are both marked as is_even_key.
entitlement->is_even_key =
key.key_slot() != CasEncryptionResponse::KeyInfo::ODD;
}
return OkStatus();
}
Status WvCasKeyFetcher::FetchEntitlements(
const EntitlementRequestParams& request_params,
std::vector<EntitlementKeyInfo>* entitlements) const {
std::string entitlement_request;
std::string entitlement_response;
Status status =
CreateEntitlementRequest(request_params, &entitlement_request);
if (!status.ok()) {
return status;
}
status = MakeHttpRequest(entitlement_request, &entitlement_response);
if (!status.ok()) {
return status;
}
return ParseEntitlementResponse(entitlement_response, entitlements);
}
} // namespace cas
} // namespace widevine

View File

@@ -11,50 +11,86 @@
#include <string>
#include "absl/flags/declare.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
namespace cas {
// Paramerters that are needed to construct an entitlement key rquest message.
// Fields:
// |content_id| uniquely identifies the content (with |content_provider|)
// |content_provider| unique std::string for provider of the content stream.
// |track_types| a vector of track ID (std::string) that specify the set of track
// types of interest; controls the entitlement keys returned by the
// server.
// |key_rotation| the encryption uses two keys if set. Two entitlement keys
// are requested for each track type.
struct EntitlementRequestParams {
std::string content_id;
std::string content_provider;
std::vector<std::string> track_types;
bool key_rotation;
};
// WV CAS KeyFetcher. Performs the communication with the Widevine License
// Server to get entitlement keys on behalf of a WvCasEcm object.
class WvCasKeyFetcher : public KeyFetcher {
class WvCasKeyFetcher {
public:
WvCasKeyFetcher() = default;
// Initialize an WvCasKeyFetcher object with required infomartion.
// Args:
// |signing_provider| name of the provider signing the CAS encryption
// request.
// |signing_key| AES key (in hex) for signing the CAS encryption request.
// |signing_iv| AES iv (in hex) for signing the CAS encryption request.
WvCasKeyFetcher(const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv);
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
~WvCasKeyFetcher() override = default;
virtual ~WvCasKeyFetcher() = default;
// Get entitlement keys from the license server. Send a
// SignedCasEncryptionRequest message to the license server, receive a
// SignedCasEncryptionResponse and return it to the caller.
// Create a SignedCasEncryptionRequest message that can be used to request
// entitlement keys from a server.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
Status RequestEntitlementKey(
const std::string& request_string,
std::string* signed_response_string) const override;
// |request_params| paramerters that are needed to request entitlement keys
// from the server.
// |signed_request_json| the created serialized SignedCasEncryptionRequest
// message.
virtual Status CreateEntitlementRequest(
const EntitlementRequestParams& request_params,
std::string* signed_request_json) const;
// Parse a CasEncryptionResponse message holding the entitlement keys for
// generating the ECM stream. The entitlement keys are used to encrypt the
// keys conveyed in the ECM. The entitlement key IDs are also part of the ECM.
// Args:
// |response_string| a serialized CasEncryptionRequest message from the server
// holding entitlement key information (or error information).
// |entitlements| a pointer holding the resulting entitlement keys parsed from
// the response string.
static Status ParseEntitlementResponse(
const std::string& http_response_json,
std::vector<EntitlementKeyInfo>* entitlements);
protected:
// Makes a HTTP request to License Server for entitlement key(s).
// Returns the HTTP response in Json format in |http_response_json|.
// Protected visibility to support unit testing.
virtual Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const;
std::string* http_response_json) const = 0;
// Conventient function that calls CreateEntitlementRequest,
// MakeHttpRequest, and ParseEntitlementResponse in sequence.
virtual Status FetchEntitlements(
const EntitlementRequestParams& request_params,
std::vector<EntitlementKeyInfo>* entitlements) const;
private:
std::string signing_provider_;
std::string signing_key_;
std::string signing_iv_;
};
} // namespace cas
} // namespace widevine
// Exposed for testing and example.
ABSL_DECLARE_FLAG(std::string, license_server);
ABSL_DECLARE_FLAG(std::string, signing_provider);
ABSL_DECLARE_FLAG(std::string, signing_key);
ABSL_DECLARE_FLAG(std::string, signing_iv);
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_

View File

@@ -9,12 +9,10 @@
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include "glog/logging.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/json_util.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/flags/flag.h"
#include "absl/strings/escaping.h"
#include "common/status.h"
#include "protos/public/media_cas_encryption.pb.h"
using testing::_;
@@ -45,64 +43,233 @@ const char kHttpResponse[] =
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
const char kTrackTypeSD[] = "SD";
const char kTrackTypeHD[] = "HD";
const char kProvider[] = "widevine";
const char kContentId[] = "21140844";
} // namespace
namespace widevine {
namespace cas {
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
class HardcodedWvCasKeyFetcher : public WvCasKeyFetcher {
public:
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
~MockWvCasKeyFetcher() override {}
HardcodedWvCasKeyFetcher(const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv)
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
~HardcodedWvCasKeyFetcher() override {}
MOCK_CONST_METHOD2(MakeHttpRequest,
Status(const std::string& signed_request_json,
std::string* http_response_json));
};
class WvCasKeyFetcherTest : public ::testing::Test {
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
public:
WvCasKeyFetcherTest() {}
void SetUp() override {
absl::SetFlag(&FLAGS_signing_provider, kSigningProvider);
absl::SetFlag(&FLAGS_signing_key, kSingingKey);
absl::SetFlag(&FLAGS_signing_iv, kSingingIv);
MockWvCasKeyFetcher(const std::string& signing_provider,
const std::string& signing_key,
const std::string& signing_iv)
: WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {}
~MockWvCasKeyFetcher() override {}
void set_report_status_ok(bool report_ok) { report_status_ok_ = report_ok; }
CHECK(
google::protobuf::TextFormat::ParseFromString(kCasEncryptionRequest, &request_));
Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const override {
SignedCasEncryptionRequest signed_request;
if (!google::protobuf::util::JsonStringToMessage(signed_request_json, &signed_request)
.ok()) {
LOG(ERROR) << "Unable to parse signed_request_json.";
return Status(error::INTERNAL);
}
response_.set_status(CasEncryptionResponse::OK);
response_.set_content_id("21140844");
CasEncryptionResponse::KeyInfo* entitlement_keys =
response_.add_entitlement_keys();
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==",
entitlement_keys->mutable_key_id()));
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
entitlement_keys->mutable_key()));
entitlement_keys->set_track_type("SD");
entitlement_keys->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
CasEncryptionRequest request;
if (!google::protobuf::util::JsonStringToMessage(signed_request.request(), &request)
.ok()) {
LOG(ERROR) << "Unable to understand signed_request_json.";
return Status(error::INTERNAL);
}
CasEncryptionResponse response;
if (!report_status_ok_) {
response.set_status(CasEncryptionResponse::INTERNAL_ERROR);
} else {
response.set_status(CasEncryptionResponse::OK);
response.set_content_id(request.content_id());
for (const auto& track_type : request.track_types()) {
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN);
// Add the Odd key.
key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
} else {
auto key = response.add_entitlement_keys();
key->set_key_id("fake_key_id.....");
key->set_key("fakefakefakefakefakefakefakefake");
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
}
}
}
std::string response_string;
if (!google::protobuf::util::MessageToJsonString(response, &response_string).ok()) {
LOG(ERROR) << "MessageToJsonString(response, &response_string)";
return Status(error::INTERNAL);
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response_string);
if (!google::protobuf::util::MessageToJsonString(signed_response, http_response_json)
.ok()) {
LOG(ERROR) << "MessageToJsonString(signed_response, http_response_json)";
return Status(error::INTERNAL);
}
return OkStatus();
}
protected:
MockWvCasKeyFetcher mock_key_fetcher_;
CasEncryptionRequest request_;
CasEncryptionResponse response_;
bool report_status_ok_ = true;
};
class WvCasKeyFetcherTest : public ::testing::Test {
public:
WvCasKeyFetcherTest() {}
protected:
EntitlementRequestParams CreateRequestParamsStruct(
const std::string& content_id, const std::string& content_provider,
const std::vector<std::string>& track_types, bool key_rotation) {
EntitlementRequestParams request_params;
request_params.content_id = content_id;
request_params.content_provider = content_provider;
request_params.track_types.assign(track_types.begin(), track_types.end());
request_params.key_rotation = key_rotation;
return request_params;
}
};
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
EXPECT_CALL(mock_key_fetcher_,
MakeHttpRequest(kSignedCasEncryptionRequest, _))
HardcodedWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation=*/false);
std::string signed_request_json;
mock_key_fetcher.CreateEntitlementRequest(request_params,
&signed_request_json);
EXPECT_EQ(signed_request_json, kSignedCasEncryptionRequest);
EXPECT_CALL(mock_key_fetcher, MakeHttpRequest(kSignedCasEncryptionRequest, _))
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
Return(OkStatus())));
std::string actual_signed_response;
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
request_.SerializeAsString(), &actual_signed_response));
EXPECT_OK(mock_key_fetcher.MakeHttpRequest(signed_request_json,
&actual_signed_response));
SignedCasEncryptionResponse expected_signed_response;
expected_signed_response.set_response(response_.SerializeAsString());
EXPECT_EQ(expected_signed_response.SerializeAsString(),
actual_signed_response);
std::vector<EntitlementKeyInfo> entitlements;
EXPECT_OK(mock_key_fetcher.ParseEntitlementResponse(actual_signed_response,
&entitlements));
ASSERT_EQ(entitlements.size(), 1);
const EntitlementKeyInfo& entitlement = entitlements.at(0);
EXPECT_EQ(entitlement.track_type, "SD");
EXPECT_EQ(entitlement.is_even_key, true);
std::string expected_key_id;
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==", &expected_key_id));
EXPECT_EQ(entitlement.key_id, expected_key_id);
std::string expected_key_value;
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
&expected_key_value));
EXPECT_EQ(entitlement.key_value, expected_key_value);
}
TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
std::string request;
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
ASSERT_OK(
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
std::string response;
ASSERT_OK(mock_key_fetcher.MakeHttpRequest(request, &response));
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.ParseEntitlementResponse(response, &entitlements));
ASSERT_EQ(entitlements.size(), 1);
const EntitlementKeyInfo& entitlement = entitlements[0];
EXPECT_EQ(entitlement.track_type, kTrackTypeSD);
EXPECT_EQ(entitlement.is_even_key, true);
EXPECT_TRUE(!entitlement.key_id.empty());
EXPECT_TRUE(!entitlement.key_value.empty());
}
TEST_F(WvCasKeyFetcherTest, OneKeyConvenientOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ false);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
ASSERT_EQ(entitlements.size(), 1);
const EntitlementKeyInfo& entitlement = entitlements[0];
EXPECT_EQ(entitlement.track_type, kTrackTypeSD);
EXPECT_EQ(entitlement.is_even_key, true);
EXPECT_TRUE(!entitlement.key_id.empty());
EXPECT_TRUE(!entitlement.key_value.empty());
}
TEST_F(WvCasKeyFetcherTest, TwoKeysOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
ASSERT_EQ(entitlements.size(), 2);
EXPECT_EQ(entitlements[0].track_type, kTrackTypeSD);
EXPECT_EQ(entitlements[1].track_type, kTrackTypeSD);
}
TEST_F(WvCasKeyFetcherTest, TwoTracksOK) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD, kTrackTypeHD},
/*key_rotation*/ true);
std::vector<EntitlementKeyInfo> entitlements;
ASSERT_OK(mock_key_fetcher.FetchEntitlements(request_params, &entitlements));
ASSERT_EQ(entitlements.size(), 4);
}
TEST_F(WvCasKeyFetcherTest, BadResponseFail) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
std::string request;
EntitlementRequestParams request_params = CreateRequestParamsStruct(
kContentId, kProvider, {kTrackTypeSD}, /*key_rotation*/ true);
ASSERT_OK(
mock_key_fetcher.CreateEntitlementRequest(request_params, &request));
std::string response;
mock_key_fetcher.set_report_status_ok(false);
ASSERT_OK(mock_key_fetcher.MakeHttpRequest(request, &response));
std::vector<EntitlementKeyInfo> entitlements;
EXPECT_EQ(error::INTERNAL,
mock_key_fetcher.ParseEntitlementResponse(response, &entitlements)
.error_code());
}
} // namespace cas

View File

@@ -20,30 +20,6 @@ using google::protobuf::util::MessageToJsonString;
namespace widevine {
namespace cas {
static const char* kWvCasStatusMessage[] = {
"OK", // OK = 0,
"",
"",
"Invalid argument", // INVALID_ARGUMENT = 3,
"",
"Not found", // NOT_FOUND = 5,
"Already exists", // ALREADY_EXISTS = 6,
"Permission denied", // PERMISSION_DENIED = 7,
"",
"",
"",
"",
"Unimplemented", // UNIMPLEMENTED = 12,
"Internal", // INTERNAL = 13,
"Unavailable", // UNAVAILABLE = 14,
};
std::string GetWvCasStatusMessage(WvCasStatus status) {
static_assert(arraysize(kWvCasStatusMessage) == NUM_WV_CAS_STATUS,
"mismatching status message and status.");
return kWvCasStatusMessage[status];
}
// Numeric value of crypto mode is the index into strings array.
static const char* kCrypoModeStrings[] = {
"AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte",
@@ -109,8 +85,8 @@ bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode) {
return false;
}
WvCasStatus CreateWvCasEncryptionRequestJson(
const WvCasEncryptionRequest& request, std::string* request_json) {
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
std::string* request_json) {
CHECK(request_json);
CasEncryptionRequest request_proto;
@@ -131,14 +107,14 @@ WvCasStatus CreateWvCasEncryptionRequestJson(
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
if (!MessageToJsonString(request_proto, request_json, print_options).ok()) {
LOG(ERROR) << "Failed to convert request message to json.";
return INTERNAL;
return Status(error::INTERNAL);
}
return OK;
return OkStatus();
}
WvCasStatus ParseWvCasEncryptionResponseJson(
const std::string& response_json, WvCasEncryptionResponse* response) {
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response) {
CHECK(response);
CasEncryptionResponse response_proto;
@@ -146,7 +122,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson(
// 'bytes' type fields.
if (!JsonStringToMessage(response_json, &response_proto).ok()) {
LOG(ERROR) << "Failed to convert response json to message.";
return INTERNAL;
return Status(error::INTERNAL);
}
response->status =
@@ -163,7 +139,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson(
response->entitlement_keys.push_back(key_info);
}
return OK;
return OkStatus();
}
} // namespace cas

View File

@@ -12,45 +12,11 @@
#include <string>
#include <vector>
#include "common/status.h"
namespace widevine {
namespace cas {
enum WvCasStatus {
// Success.
OK = 0,
// Client specified an invalid argument.
INVALID_ARGUMENT = 3,
// Some requested entity (e.g., file or directory) was not found.
NOT_FOUND = 5,
// Some entity that we attempted to create (e.g., file or directory)
// already exists.
ALREADY_EXISTS = 6,
// The caller does not have permission to execute the specified
// operation.
PERMISSION_DENIED = 7,
// Operation is not implemented or not supported/enabled in this service.
UNIMPLEMENTED = 12,
// Internal errors. Means some invariants expected by underlying
// system has been broken. If you see one of these errors,
// something is very broken.
INTERNAL = 13,
// Operation is not implemented or not supported/enabled in this service.
UNAVAILABLE = 14,
// Number of errors.
NUM_WV_CAS_STATUS,
};
// Returns the message std::string for the given WvCasStatus.
std::string GetWvCasStatusMessage(WvCasStatus status);
// Crypto mode for encryption / decryption. ENUM value should be consistent with
// ECM V2 definition. Largest supported value for this CryptoMode ENUM is 15.
enum class CryptoMode : int {
@@ -77,6 +43,28 @@ bool ScramblingLevelToString(ScramblingLevel mode, std::string* str);
// Returns false if str is not a valid ScramblingLevel.
bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode);
// Declare Content IV size in an ECM stream.
// Content IVs may be encoded as 8 or 16 random bytes. The receiver is
// responsible for appending 8 zeros to an 8-byte IV. All content IVs, once the
// size is declared, must be the same size. Wrapped key IVs are always 16 bytes.
enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// Information needed for the injected entitlement keys. Used for Ecm
// initialization.
// Fields:
// |track_type| the track that the key is being used to encrypt.
// |is_even_key| if true, this entitlement is an even key. For the single key
// case (no key rotation), is_even_key is always true.
// |key_id| key ID for this entitlement key, must be 16 bytes.
// |key_value| entitlement key value, must be 32 bytes. Used to decrypt
// content keys.
struct EntitlementKeyInfo {
std::string track_type;
bool is_even_key;
std::string key_id; // must be 16 bytes.
std::string key_value; // must be 32 bytes.
};
struct WvCasEncryptionRequest {
std::string content_id;
std::string provider;
@@ -124,15 +112,15 @@ struct WvCasEncryptionResponse {
// request JSON message.
// And that signed JSON message can be sent to Widevine license server for
// acquiring entitlement keys.
WvCasStatus CreateWvCasEncryptionRequestJson(
const WvCasEncryptionRequest& request, std::string* request_json);
Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request,
std::string* request_json);
// Parses a WvCasEncryptionResponse in JSON format, returns a
// WvCasEncryptionResponse.
// |response_json| is supposed to be the 'response' field in the signed
// response from Widevine license server.
WvCasStatus ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response);
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response);
} // namespace cas
} // namespace widevine

View File

@@ -8,22 +8,12 @@
#include "media_cas_packager_sdk/public/wv_cas_types.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace widevine {
namespace cas {
TEST(WvCasTypesTest, GetWvCasStatusMessage) {
EXPECT_EQ("OK", GetWvCasStatusMessage(OK));
EXPECT_EQ("Invalid argument", GetWvCasStatusMessage(INVALID_ARGUMENT));
EXPECT_EQ("Not found", GetWvCasStatusMessage(NOT_FOUND));
EXPECT_EQ("Already exists", GetWvCasStatusMessage(ALREADY_EXISTS));
EXPECT_EQ("Permission denied", GetWvCasStatusMessage(PERMISSION_DENIED));
EXPECT_EQ("Unimplemented", GetWvCasStatusMessage(UNIMPLEMENTED));
EXPECT_EQ("Internal", GetWvCasStatusMessage(INTERNAL));
EXPECT_EQ("Unavailable", GetWvCasStatusMessage(UNAVAILABLE));
}
TEST(WvCasTypesTest, CryptoModeToString) {
std::string crypto_mode;
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCtr, &crypto_mode));
@@ -86,8 +76,7 @@ TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) {
request.key_rotation = true;
std::string actual_request_json;
EXPECT_EQ(OK,
CreateWvCasEncryptionRequestJson(request, &actual_request_json));
EXPECT_OK(CreateWvCasEncryptionRequestJson(request, &actual_request_json));
// Content_id has been base64 encoded.
std::string expected_request_json =
@@ -107,8 +96,7 @@ TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) {
"\"key_slot\":\"ODD\"}]}";
WvCasEncryptionResponse actual_response;
EXPECT_EQ(OK,
ParseWvCasEncryptionResponseJson(response_json, &actual_response));
EXPECT_OK(ParseWvCasEncryptionResponseJson(response_json, &actual_response));
EXPECT_EQ(WvCasEncryptionResponse::Status::OK, actual_response.status);
// 21140844 is base64 decode of "MjExNDA4NDQ=".

View File

@@ -46,11 +46,9 @@ ABSL_FLAG(int32_t, number_of_content_keys, 2,
"key) or 2 (key rotation enabled). It also sets LeadCw as "
"number_of_content_keys - 1.");
ABSL_FLAG(std::string, crypto_mode, "AesCtr",
"Encryption mode. Choices are \"AesCtr\", \"AesCbc\", "
"\"DvbCsa2\", \"DvbCsa3\", \"AesOfb\", \"AesScte\".");
ABSL_FLAG(bool, use_fixed_fetcher, false,
"If set, use fixed fetcher to fetch mocked entitlement licenses when "
"needed for testing purposes.");
"Default encryption mode if not provided in API calls. Choices are "
"\"AesCtr\", \"AesCbc\", \"DvbCsa2\", \"DvbCsa3\", \"AesOfb\" and "
"\"AesScte\".");
#define LISTEN_QUEUE_SIZE (20)
#define BUFFER_SIZE (1024)
@@ -73,7 +71,6 @@ void BuildEcmgConfig(EcmgConfig* config) {
CHECK(StringToCryptoMode(absl::GetFlag(FLAGS_crypto_mode),
&config->crypto_mode))
<< "Unknown crypto mode.";
config->use_fixed_fetcher = absl::GetFlag(FLAGS_use_fixed_fetcher);
}
void PrintMessage(const std::string& description, const char* const message,

View File

@@ -43,6 +43,9 @@ ABSL_FLAG(int32_t, data_id, 0, "EMMG data_id.");
ABSL_FLAG(int32_t, data_type, 1, "EMMG data_type");
ABSL_FLAG(std::string, content_provider, "", "Content provider");
ABSL_FLAG(std::string, content_id, "", "Content id");
ABSL_FLAG(int32_t, bandwidth, 100, "Requested bandwidth in kbps");
ABSL_FLAG(int32_t, max_num_message, 100,
"Maximum number of messages that can be sent");
#define BUFFER_SIZE (1024)
@@ -56,6 +59,8 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig *config) {
config->data_type = absl::GetFlag(FLAGS_data_type);
config->content_provider = absl::GetFlag(FLAGS_content_provider);
config->content_id = absl::GetFlag(FLAGS_content_id);
config->bandwidth = absl::GetFlag(FLAGS_bandwidth);
config->max_num_message = absl::GetFlag(FLAGS_max_num_message);
}
int main(int argc, char **argv) {

View File

@@ -10,6 +10,11 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "binary_release_files",
srcs = glob(["**"]),
)
proto_library(
name = "media_cas_encryption_proto",
srcs = ["media_cas_encryption.proto"],