diff --git a/BUILD b/BUILD index 9af40a2..dec0b5d 100644 --- a/BUILD +++ b/BUILD @@ -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"], ) diff --git a/common/core_message_util.cc b/common/core_message_util.cc index 6c66a87..8130204 100644 --- a/common/core_message_util.cc +++ b/common/core_message_util.cc @@ -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 diff --git a/example/BUILD b/example/BUILD index e8321bc..4c06295 100644 --- a/example/BUILD +++ b/example/BUILD @@ -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", ], ) diff --git a/example/test_ecmg_messages.h b/example/test_ecmg_messages.h index 77a018e..6b81fa9 100644 --- a/example/test_ecmg_messages.h +++ b/example/test_ecmg_messages.h @@ -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[] = { diff --git a/example/test_emmg_messages.h b/example/test_emmg_messages.h index 7fabcff..aeac11a 100644 --- a/example/test_emmg_messages.h +++ b/example/test_emmg_messages.h @@ -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 diff --git a/example/wv_cas_ecm_example.cc b/example/wv_cas_ecm_example.cc index 9f06fae..c59e00e 100644 --- a/example/wv_cas_ecm_example.cc +++ b/example/wv_cas_ecm_example.cc @@ -17,63 +17,125 @@ #include #include +#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, + ¶ms.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 CreateInjectedEntitlements(bool key_rotation) { + std::vector 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 CreateContentKeyInfo(bool key_rotation, + int content_iv_size) { + std::vector 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 entitlements = + CreateInjectedEntitlements(kKeyRotation); + widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements); + + std::vector 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: "; diff --git a/example/wv_cas_key_fetcher_example.cc b/example/wv_cas_key_fetcher_example.cc index f79589c..7f1e84a 100644 --- a/example/wv_cas_key_fetcher_example.cc +++ b/example/wv_cas_key_fetcher_example.cc @@ -7,65 +7,86 @@ //////////////////////////////////////////////////////////////////////////////// +#include #include +#include -#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 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; } diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index b53a9cb..72844f9 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -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 = [ diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index 5de63d5..e9a2e0d 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -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& injected_entitlements) { + const std::vector& 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& 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& keys) const { if (!initialized_) { @@ -451,103 +404,6 @@ std::string Ecm::SerializeEcm(const std::vector& 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 " diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index ff2300b..fea7db5 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -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 track_types; + std::vector 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& 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& 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{}); + const EntitlementKeyIdValue& key) { + auto emplaced = entitlement_keys_.emplace( + track_type, std::list{}); 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 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> entitlement_keys_; + // Maps from track_type to one/two EntitlementKeyIdValue with even key first. + std::map> entitlement_keys_; }; } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecm_generator_test.cc b/media_cas_packager_sdk/internal/ecm_generator_test.cc index 8f2eb0a..f5f16ef 100644 --- a/media_cas_packager_sdk/internal/ecm_generator_test.cc +++ b/media_cas_packager_sdk/internal/ecm_generator_test.cc @@ -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(); 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 entitlements; + ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response, + &entitlements)); + + ecm_ = absl::make_unique(); + 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_; EcmInitParameters ecm_init_params_; diff --git a/media_cas_packager_sdk/internal/ecm_test.cc b/media_cas_packager_sdk/internal/ecm_test.cc index 7773384..390323b 100644 --- a/media_cas_packager_sdk/internal/ecm_test.cc +++ b/media_cas_packager_sdk/internal/ecm_test.cc @@ -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(¶ms_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(¶ms_one_key_, kTrackTypeSD, kIvSize16); + InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_one_key_, kIvSize8); + ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_})); + EntitledKeyInfo key1 = valid1_iv_16_8_; - InitParams(¶ms_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(¶ms_two_keys_, kIvSize8); + ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_, + injected_entitlement_two_})); + EntitledKeyInfo key1 = valid1_iv_16_8_; - InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_two_keys_, kTrackTypeSD, kIvSize16); + InitParams(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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(¶ms_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 diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.cc b/media_cas_packager_sdk/internal/ecmg_client_handler.cc index 78d9bc8..2941c72 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.cc @@ -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(¶ms->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 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(); - 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 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(); - } else { - key_fetcher_ = absl::make_unique(); - } - } - 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(); diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.h b/media_cas_packager_sdk/internal/ecmg_client_handler.h index e5f0261..5b9862d 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.h +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.h @@ -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 error_status; + std::vector 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> streams_info_; - std::unique_ptr key_fetcher_; }; } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc index 263cac0..7986074 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc @@ -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(&config_); } @@ -97,8 +94,6 @@ class EcmgClientHandlerTest : public ::testing::Test { uint8_t age_restriction, const std::string& crypto_mode, const std::vector& track_types, - const std::string& content_id, - const std::string& content_provider, const std::vector& 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(content_id.c_str()), - content_id.size(), message, message_length); - } - if (!content_provider.empty()) { - AddParam(CONTENT_PROVIDER, - reinterpret_cast(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 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 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_, diff --git a/media_cas_packager_sdk/internal/ecmg_constants.h b/media_cas_packager_sdk/internal/ecmg_constants.h index 59dce27..13e4ce5 100644 --- a/media_cas_packager_sdk/internal/ecmg_constants.h +++ b/media_cas_packager_sdk/internal/ecmg_constants.h @@ -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_ diff --git a/media_cas_packager_sdk/internal/emmg.cc b/media_cas_packager_sdk/internal/emmg.cc index 55be4cd..bb18540 100644 --- a/media_cas_packager_sdk/internal/emmg.cc +++ b/media_cas_packager_sdk/internal/emmg.cc @@ -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(¶m_type, response + offset); + offset += PARAMETER_TYPE_SIZE; + BigEndianToHost16(¶m_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(¶ms->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(¶ms->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(¶ms->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(¶ms->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, ¶ms); + 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((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); diff --git a/media_cas_packager_sdk/internal/emmg.h b/media_cas_packager_sdk/internal/emmg.h index 64e65a6..ce5c1af 100644 --- a/media_cas_packager_sdk/internal/emmg.h +++ b/media_cas_packager_sdk/internal/emmg.h @@ -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); diff --git a/media_cas_packager_sdk/internal/emmg_constants.h b/media_cas_packager_sdk/internal/emmg_constants.h index 01d99b8..055d964 100644 --- a/media_cas_packager_sdk/internal/emmg_constants.h +++ b/media_cas_packager_sdk/internal/emmg_constants.h @@ -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_ diff --git a/media_cas_packager_sdk/internal/emmg_test.cc b/media_cas_packager_sdk/internal/emmg_test.cc index 7ef212a..b47967a 100644 --- a/media_cas_packager_sdk/internal/emmg_test.cc +++ b/media_cas_packager_sdk/internal/emmg_test.cc @@ -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(&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(), diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc index ee24111..90daacf 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc @@ -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(); } diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.h b/media_cas_packager_sdk/internal/fixed_key_fetcher.h index b8a62da..0aeb382 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.h +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.h @@ -12,20 +12,26 @@ #include #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_; diff --git a/media_cas_packager_sdk/internal/key_fetcher.h b/media_cas_packager_sdk/internal/key_fetcher.h deleted file mode 100644 index a387471..0000000 --- a/media_cas_packager_sdk/internal/key_fetcher.h +++ /dev/null @@ -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 - -#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_ diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index b299c26..f8c5757 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc index 31a5cf3..b3d0c9d 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc @@ -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 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 { diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h index 7323d56..1a044fc 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h @@ -14,7 +14,7 @@ #include #include -#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 diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc index e0244d6..af14eee 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc new file mode 100644 index 0000000..432a9cb --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc @@ -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(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 diff --git a/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h new file mode 100644 index 0000000..9d98c90 --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h @@ -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 + +#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_ diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index 6b8c8dc..6a8f2cc 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -8,320 +8,85 @@ #include "media_cas_packager_sdk/public/wv_cas_ecm.h" -#include -#include -#include - #include -#include #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& 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(); - 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 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(); - 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 diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index fafc608..fe59137 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -10,102 +10,91 @@ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ #include +#include #include +#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& 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 injected_entitlements_; }; } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc index 9fb0327..04d6afb 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -8,385 +8,194 @@ #include "media_cas_packager_sdk/public/wv_cas_ecm.h" -#include +#include +#include + +#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 CreateInjectedEntitlements( + bool key_rotation) { + std::vector 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 CreateContentKeyInfo(bool key_rotation, + int content_iv_size) { + std::vector 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 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 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 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 entitlements = + CreateInjectedEntitlements(/*key_rotation*/ false); + WvCasEcm wv_cas_ecm(params, entitlements); + + std::vector 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 entitlements = + CreateInjectedEntitlements(key_rotation_); + WvCasEcm wv_cas_ecm(params, entitlements); + std::vector 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 diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc index 6b9e7fb..a43af2f 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc @@ -11,49 +11,61 @@ #include #include -#include +#include +#include + #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* 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(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* 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 diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h index 815c93d..35c9ac4 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h @@ -11,50 +11,86 @@ #include -#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 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* 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* 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_ diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc index 8cb55c5..b0a96bc 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc @@ -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& 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 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 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 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 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 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 entitlements; + EXPECT_EQ(error::INTERNAL, + mock_key_fetcher.ParseEntitlementResponse(response, &entitlements) + .error_code()); } } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_types.cc b/media_cas_packager_sdk/public/wv_cas_types.cc index 95694e1..afe0f84 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.cc +++ b/media_cas_packager_sdk/public/wv_cas_types.cc @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index cbea067..e5c882c 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -12,45 +12,11 @@ #include #include +#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 diff --git a/media_cas_packager_sdk/public/wv_cas_types_test.cc b/media_cas_packager_sdk/public/wv_cas_types_test.cc index 00078e1..94b8078 100644 --- a/media_cas_packager_sdk/public/wv_cas_types_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_types_test.cc @@ -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=". diff --git a/media_cas_packager_sdk/public/wv_ecmg.cc b/media_cas_packager_sdk/public/wv_ecmg.cc index a20d4ad..8b84469 100644 --- a/media_cas_packager_sdk/public/wv_ecmg.cc +++ b/media_cas_packager_sdk/public/wv_ecmg.cc @@ -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, diff --git a/media_cas_packager_sdk/public/wv_emmg.cc b/media_cas_packager_sdk/public/wv_emmg.cc index 16ba392..60e97b9 100644 --- a/media_cas_packager_sdk/public/wv_emmg.cc +++ b/media_cas_packager_sdk/public/wv_emmg.cc @@ -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) { diff --git a/protos/public/BUILD b/protos/public/BUILD index fac8889..cd40ec4 100644 --- a/protos/public/BUILD +++ b/protos/public/BUILD @@ -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"],