From 62777d7d3b90143572dcd0fafc086f870b1bd901 Mon Sep 17 00:00:00 2001 From: Lu Chen Date: Thu, 4 Mar 2021 14:35:08 -0800 Subject: [PATCH] Support for group license Content keys in ECM v3 can now additionally be encrypted by group entitlement keys. --- WORKSPACE | 11 +- common/BUILD | 66 ++++ common/aes_cbc_util.cc | 1 + common/crypto_util.cc | 5 +- common/crypto_util.h | 1 + common/ec_key.cc | 2 + common/hash_algorithm.h | 2 +- common/rsa_key.cc | 7 + common/rsa_key.h | 1 + common/security_profile_list.h | 2 + common/sha_util.cc | 10 + common/sha_util.h | 3 + example/wv_cas_ecm_example.cc | 10 +- example/wv_cas_emm_example.cc | 1 + example/wv_ecmg_example.cc | 39 +- media_cas_packager_sdk/internal/BUILD | 31 +- media_cas_packager_sdk/internal/ecm.cc | 134 ++++--- media_cas_packager_sdk/internal/ecm.h | 61 ++- .../internal/ecm_generator.cc | 138 ------- .../internal/ecm_generator.h | 92 ----- .../internal/ecm_generator_test.cc | 351 ------------------ .../internal/ecm_serializer.h | 9 + .../internal/ecm_serializer_v2_test.cc | 1 + .../internal/ecm_serializer_v3.cc | 80 ++++ .../internal/ecm_serializer_v3_test.cc | 166 +++++++++ media_cas_packager_sdk/internal/ecm_test.cc | 182 +++++++-- .../internal/ecmg_client_handler.cc | 299 ++++++++++----- .../internal/ecmg_client_handler.h | 27 +- .../internal/ecmg_client_handler_test.cc | 245 ++++++++++-- .../internal/ecmg_constants.h | 1 + media_cas_packager_sdk/internal/emm.cc | 1 + media_cas_packager_sdk/internal/emm.h | 1 + media_cas_packager_sdk/internal/emm_test.cc | 4 +- media_cas_packager_sdk/internal/emmg.cc | 1 + media_cas_packager_sdk/internal/emmg.h | 1 + media_cas_packager_sdk/internal/emmg_test.cc | 1 + media_cas_packager_sdk/internal/mpeg2ts.h | 1 + .../internal/simulcrypt_util.cc | 5 +- .../internal/simulcrypt_util.h | 5 +- media_cas_packager_sdk/internal/ts_packet.cc | 1 + media_cas_packager_sdk/internal/ts_packet.h | 9 +- media_cas_packager_sdk/internal/util.cc | 1 + media_cas_packager_sdk/internal/util.h | 1 + media_cas_packager_sdk/internal/util_test.cc | 2 + media_cas_packager_sdk/public/BUILD | 2 + .../public/wv_cas_ca_descriptor.cc | 1 + .../public/wv_cas_ca_descriptor.h | 4 +- .../public/wv_cas_ca_descriptor_test.cc | 2 + .../public/wv_cas_curl_key_fetcher.cc | 2 + media_cas_packager_sdk/public/wv_cas_ecm.cc | 8 +- media_cas_packager_sdk/public/wv_cas_ecm.h | 7 + .../public/wv_cas_ecm_test.cc | 9 +- .../public/wv_cas_ecmg_client_handler.cc | 22 +- .../public/wv_cas_ecmg_client_handler.h | 5 +- .../public/wv_cas_ecmg_client_handler_test.cc | 30 +- media_cas_packager_sdk/public/wv_cas_emm.cc | 2 + media_cas_packager_sdk/public/wv_cas_emm.h | 1 + .../public/wv_cas_emm_test.cc | 1 + media_cas_packager_sdk/public/wv_cas_types.h | 46 ++- media_cas_packager_sdk/public/wv_ecmg.cc | 33 +- media_cas_packager_sdk/public/wv_emmg.cc | 1 + protos/public/hash_algorithm.proto | 1 + protos/public/media_cas.proto | 27 ++ protos/public/media_cas_encryption.proto | 9 +- strings/serialize_test.cc | 2 + util/endian/endian_test.cc | 2 + 66 files changed, 1275 insertions(+), 954 deletions(-) delete mode 100644 media_cas_packager_sdk/internal/ecm_generator.cc delete mode 100644 media_cas_packager_sdk/internal/ecm_generator.h delete mode 100644 media_cas_packager_sdk/internal/ecm_generator_test.cc diff --git a/WORKSPACE b/WORKSPACE index ba0d5d3..b54cb3c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -21,14 +21,21 @@ git_repository( git_repository( name = "com_google_protobuf", remote = "https://github.com/google/protobuf.git", - tag = "v3.8.0", + tag = "v3.14.0", +) + +# Bazel custom build rule support. +git_repository( + name = "rules_python", + remote = "https://github.com/bazelbuild/rules_python.git", + tag = "0.1.0" ) # Bazel custom build rule support. git_repository( name = "bazel_skylib", remote = "https://github.com/bazelbuild/bazel-skylib.git", - tag = "0.8.0", + tag = "1.0.3", ) # Protobuf library support. Not included in the recent protobuf release. diff --git a/common/BUILD b/common/BUILD index 02b0d27..a513e82 100644 --- a/common/BUILD +++ b/common/BUILD @@ -34,6 +34,7 @@ cc_library( hdrs = ["playready_interface.h"], deps = [ "//util:error_space", + "//protos/public:external_license_cc_proto", "//protos/public:license_protocol_cc_proto", ], ) @@ -43,6 +44,7 @@ cc_library( hdrs = ["playready_sdk_impl.h"], deps = [ ":playready_interface", + "//protos/public:external_license_cc_proto", "//protos/public:license_protocol_cc_proto", ], ) @@ -352,6 +354,28 @@ cc_library( ], ) +cc_library( + name = "device_certificate_serial_number_util", + srcs = ["device_certificate_serial_number_util.cc"], + hdrs = ["device_certificate_serial_number_util.h"], + deps = [ + ":sha_util", + "//base", + "@abseil_repo//absl/types:optional", + ], +) + +cc_test( + name = "device_certificate_serial_number_util_test", + srcs = ["device_certificate_serial_number_util_test.cc"], + deps = [ + ":device_certificate_serial_number_util", + "//testing:gunit", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) + cc_library( name = "private_key_util", hdrs = ["private_key_util.h"], @@ -1218,3 +1242,45 @@ cc_test( "//protos/public:remote_attestation_cc_proto", ], ) + +cc_library( + name = "signed_message_util", + srcs = ["signed_message_util.cc"], + hdrs = ["signed_message_util.h"], + deps = [ + ":client_cert", + ":client_id_util", + ":device_status_list", + ":error_space", + ":status", + ":wvm_token_handler", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:license_protocol_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", + ], +) + +cc_test( + name = "signed_message_util_test", + srcs = ["signed_message_util_test.cc"], + deps = [ + ":device_status_list", + ":error_space", + ":rsa_key", + ":rsa_test_keys", + ":signed_message_util", + ":status", + "//external:protobuf", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:license_protocol_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", + ], +) diff --git a/common/aes_cbc_util.cc b/common/aes_cbc_util.cc index 79d6522..9658f66 100644 --- a/common/aes_cbc_util.cc +++ b/common/aes_cbc_util.cc @@ -8,6 +8,7 @@ #include "common/aes_cbc_util.h" +#include #include #include diff --git a/common/crypto_util.cc b/common/crypto_util.cc index a95f56e..3f7c0ef 100644 --- a/common/crypto_util.cc +++ b/common/crypto_util.cc @@ -10,6 +10,8 @@ #include "common/crypto_util.h" +#include + #include "glog/logging.h" #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" @@ -155,7 +157,8 @@ std::string DeriveKey(absl::string_view key, absl::string_view label, message.append(1, (size_bits >> 16) & 0xFF); message.append(1, (size_bits >> 8) & 0xFF); message.append(1, size_bits & 0xFF); - if (CMAC_Update(cmac_ctx, reinterpret_cast(message.data()), + if (CMAC_Update(cmac_ctx, + reinterpret_cast(message.data()), message.size())) { size_t reslen; unsigned char res[AES_BLOCK_SIZE]; diff --git a/common/crypto_util.h b/common/crypto_util.h index a78a013..8968cd9 100644 --- a/common/crypto_util.h +++ b/common/crypto_util.h @@ -12,6 +12,7 @@ #ifndef COMMON_CRYPTO_UTIL_H_ #define COMMON_CRYPTO_UTIL_H_ +#include #include #include "absl/strings/escaping.h" diff --git a/common/ec_key.cc b/common/ec_key.cc index 4b22d9a..c897e10 100644 --- a/common/ec_key.cc +++ b/common/ec_key.cc @@ -62,6 +62,8 @@ std::string GetMessageDigest(const std::string& message, case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kSha256: return widevine::Sha256_Hash(message); + case widevine::HashAlgorithm::kSha384: + return widevine::Sha384_Hash(message); case widevine::HashAlgorithm::kSha1: LOG(ERROR) << "Unexpected hash algorithm: " << static_cast(hash_algorithm); diff --git a/common/hash_algorithm.h b/common/hash_algorithm.h index 356b190..9c4b6d3 100644 --- a/common/hash_algorithm.h +++ b/common/hash_algorithm.h @@ -11,7 +11,7 @@ namespace widevine { -enum class HashAlgorithm { kUnspecified, kSha1, kSha256 }; +enum class HashAlgorithm { kUnspecified, kSha1, kSha256, kSha384 }; } // namespace widevine diff --git a/common/rsa_key.cc b/common/rsa_key.cc index a54e9b1..9bcabd5 100644 --- a/common/rsa_key.cc +++ b/common/rsa_key.cc @@ -25,7 +25,10 @@ #include "common/rsa_key.h" +#include + #include "glog/logging.h" +#include "openssl/asn1.h" #include "openssl/bn.h" #include "openssl/digest.h" #include "openssl/err.h" @@ -60,6 +63,8 @@ std::string GetMessageDigest(const std::string& message, case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kSha1: return widevine::Sha1_Hash(message); + case widevine::HashAlgorithm::kSha384: + return widevine::Sha384_Hash(message); case widevine::HashAlgorithm::kSha256: return widevine::Sha256_Hash(message); } @@ -73,6 +78,8 @@ const EVP_MD* GetHashMd(widevine::HashAlgorithm hash_algorithm) { case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kSha1: return EVP_sha1(); + case widevine::HashAlgorithm::kSha384: + return EVP_sha384(); case widevine::HashAlgorithm::kSha256: return EVP_sha256(); } diff --git a/common/rsa_key.h b/common/rsa_key.h index 0a8f9ba..391f57a 100644 --- a/common/rsa_key.h +++ b/common/rsa_key.h @@ -14,6 +14,7 @@ #ifndef COMMON_RSA_KEY_H_ #define COMMON_RSA_KEY_H_ +#include #include #include diff --git a/common/security_profile_list.h b/common/security_profile_list.h index 37f4dfd..e1c91c1 100644 --- a/common/security_profile_list.h +++ b/common/security_profile_list.h @@ -14,6 +14,8 @@ #ifndef COMMON_SECURITY_PROFILE_LIST_H_ #define COMMON_SECURITY_PROFILE_LIST_H_ +#include + #include "absl/synchronization/mutex.h" #include "common/hash_algorithm.h" #include "common/status.h" diff --git a/common/sha_util.cc b/common/sha_util.cc index 0844855..cb0510f 100644 --- a/common/sha_util.cc +++ b/common/sha_util.cc @@ -8,6 +8,8 @@ #include "common/sha_util.h" +#include + #include #include "openssl/sha.h" @@ -29,6 +31,14 @@ std::string Sha256_Hash(const std::string& message) { return digest; } +std::string Sha384_Hash(const std::string& message) { + std::string digest; + digest.resize(SHA384_DIGEST_LENGTH); + SHA384(reinterpret_cast(message.data()), message.size(), + reinterpret_cast(&digest[0])); + return digest; +} + std::string Sha512_Hash(const std::string& message) { std::string digest; digest.resize(SHA512_DIGEST_LENGTH); diff --git a/common/sha_util.h b/common/sha_util.h index b9ed829..482efcb 100644 --- a/common/sha_util.h +++ b/common/sha_util.h @@ -21,6 +21,9 @@ std::string Sha1_Hash(const std::string& message); // Calculates SHA256 hash. std::string Sha256_Hash(const std::string& message); +// Calculates SHA384 hash. +std::string Sha384_Hash(const std::string& message); + // Calculate SHA512 hash. std::string Sha512_Hash(const std::string& message); diff --git a/example/wv_cas_ecm_example.cc b/example/wv_cas_ecm_example.cc index 88b38d0..fe7bb40 100644 --- a/example/wv_cas_ecm_example.cc +++ b/example/wv_cas_ecm_example.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -166,11 +167,12 @@ int main(int argc, char** argv) { std::string ecm; widevine::Status status; if (kKeyRotation) { - status = wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], - kDefaultTrackTypeSd, &ecm); + status = + wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], + kDefaultTrackTypeSd, /*group_ids=*/{}, &ecm); } else { - status = wv_cas_ecm.GenerateSingleKeyEcm(content_keys[0], - kDefaultTrackTypeSd, &ecm); + status = wv_cas_ecm.GenerateSingleKeyEcm( + content_keys[0], kDefaultTrackTypeSd, /*group_ids=*/{}, &ecm); } if (!status.ok()) { diff --git a/example/wv_cas_emm_example.cc b/example/wv_cas_emm_example.cc index a88457f..defbc89 100644 --- a/example/wv_cas_emm_example.cc +++ b/example/wv_cas_emm_example.cc @@ -8,6 +8,7 @@ // Example of how to use the wv_cas_emm library. +#include #include #include #include diff --git a/example/wv_ecmg_example.cc b/example/wv_ecmg_example.cc index 409f15d..17f7f11 100644 --- a/example/wv_ecmg_example.cc +++ b/example/wv_ecmg_example.cc @@ -9,12 +9,14 @@ // Example of Simulcrypt ECMG with custom entitlement key fetcher. #include +#include #include #include #include #include #include +#include #include #include #include @@ -76,7 +78,6 @@ void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) { char response[kBufferSizeBytes]; while (true) { bzero(request, kBufferSizeBytes); - bzero(response, kBufferSizeBytes); size_t response_length = 0; size_t request_length = recv(socket_fd, request, kBufferSizeBytes, 0); if (request_length == 0) { @@ -88,12 +89,23 @@ void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) { return; } PrintMessage("Request", request, request_length); - ecmg->HandleRequest(kBufferSizeBytes, request, kBufferSizeBytes, response, - &response_length); - PrintMessage("Response", response, response_length); - if (send(socket_fd, response, response_length, 0) < 0) { - std::cerr << "Failed to send response to client." << std::endl; - return; + size_t processed_total_length = 0; + while (processed_total_length < request_length) { + bzero(response, kBufferSizeBytes); + size_t processed_length = 0; + ecmg->HandleRequest(kBufferSizeBytes - processed_total_length, + request + processed_total_length, kBufferSizeBytes, + response, response_length, processed_length); + if (processed_length == 0) { + std::cerr << "Failed to process the request" << std::endl; + return; + } + processed_total_length += processed_length; + PrintMessage("Response", response, response_length); + if (send(socket_fd, response, response_length, 0) < 0) { + std::cerr << "Failed to send response to client" << std::endl; + return; + } } } } @@ -118,10 +130,10 @@ std::vector MyEntitlementKeyFetcherFun(uint16_t channel_id, constexpr char entitlement_key_id_odd[] = "fake_key_id2...."; constexpr char entitlement_key_value_odd[] = "fakefakefakefakefakefakefake2..."; - return {{track_types_hd, /*is_even_key=*/true, entitlement_key_id_even, - entitlement_key_value_even}, - {track_types_hd, /*is_even_key=*/false, entitlement_key_id_odd, - entitlement_key_value_odd}}; + return {{track_types_hd, /*group_id=*/"", /*is_even_key=*/true, + entitlement_key_id_even, entitlement_key_value_even}, + {track_types_hd, /*group_id=*/"", /*is_even_key=*/false, + entitlement_key_id_odd, entitlement_key_value_odd}}; } int create_listen_socket_fd() { @@ -181,6 +193,9 @@ int main(int argc, char** argv) { break; } + // Ignoring SIGCHLD signal to prevent Zombie processes. + signal(SIGCHLD, SIG_IGN); + // While loop to serve different client connections. while (true) { struct sockaddr_in client_address; @@ -190,7 +205,7 @@ int main(int argc, char** argv) { &client_address_size); std::cout << "\nTCP connection " << client_socket_fd << " start" << std::endl; - if (client_socket_fd < 0) { + if (client_socket_fd < 0 && errno != EINTR) { std::cerr << "Failed to accept connection request from client" << std::endl; } else { diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index ca747d7..da8be16 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -55,34 +55,6 @@ cc_test( ], ) -cc_library( - name = "ecm_generator", - srcs = ["ecm_generator.cc"], - hdrs = ["ecm_generator.h"], - deps = [ - ":ecm", - "//base", - "//common:status", - ], -) - -cc_test( - name = "ecm_generator_test", - size = "small", - srcs = ["ecm_generator_test.cc"], - deps = [ - ":ecm_generator", - ":fixed_key_fetcher", - "//base", - "//external:protobuf", - "//testing:gunit_main", - "@abseil_repo//absl/memory", - "@abseil_repo//absl/strings", - "//common:aes_cbc_util", - "//protos/public:media_cas_encryption_cc_proto", - ], -) - cc_library( name = "ecmg_client_handler", srcs = ["ecmg_client_handler.cc"], @@ -98,6 +70,7 @@ cc_library( ":util", "//base", "@abseil_repo//absl/container:flat_hash_map", + "@abseil_repo//absl/container:flat_hash_set", "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", "@abseil_repo//absl/types:optional", @@ -123,6 +96,7 @@ cc_test( "@abseil_repo//absl/strings", "@abseil_repo//absl/strings:str_format", "@abseil_repo//absl/types:span", + "//common:status", "//example:test_ecmg_messages", ], ) @@ -309,6 +283,7 @@ cc_library( deps = [ ":ecm_serializer", "//base", + "@abseil_repo//absl/container:flat_hash_set", "@abseil_repo//absl/status", "@abseil_repo//absl/status:statusor", "@abseil_repo//absl/strings", diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index 3cacd6a..7eada83 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -11,6 +11,9 @@ #include #include +#include +#include +#include #include "glog/logging.h" #include "absl/memory/memory.h" @@ -76,10 +79,14 @@ Status Ecm::Initialize( return {error::INVALID_ARGUMENT, "Invalid CA system ID."}; } - ClearEntitlementKeys(); + entitlement_keys_.clear(); for (const auto& entitlement : injected_entitlements) { - PushEntitlementKey(entitlement.track_type, entitlement.is_even_key, - {entitlement.key_id, entitlement.key_value}); + std::vector& map_entry = + entitlement_keys_[std::make_pair(entitlement.track_type, + entitlement.group_id)]; + map_entry.insert( + entitlement.is_even_key ? map_entry.begin() : map_entry.end(), + {entitlement.key_id, entitlement.key_value}); } if (!CheckEntitlementKeys()) { @@ -114,13 +121,14 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) { Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const { absl::ReaderMutexLock lock(&ecm_params_mutex_); if (!initialized_) { return {error::INTERNAL, "Not initialized."}; } - if (!HaveEntitlementKeys()) { + if (entitlement_keys_.empty()) { return {error::INTERNAL, "Need entitlement key."}; } if (even_key == nullptr) { @@ -134,31 +142,45 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, return {error::INVALID_ARGUMENT, "odd_key can not be null as key rotation is enabled."}; } - if (serialized_ecm == nullptr) { return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."}; } - - std::vector keys = {even_key}; - if (odd_key != nullptr) { - keys.push_back(odd_key); - } - Status status = WrapEntitledKeys(track_type, keys); - if (!status.ok()) { - return status; - } + const bool has_odd_key = odd_key != nullptr; EcmSerializerParams serializer_params; serializer_params.crypto_mode = crypto_mode_; serializer_params.age_restriction = age_restriction_; serializer_params.cas_id = cas_id_; - serializer_params.even_key = {even_key->entitlement_key_id, even_key->key_id, - even_key->wrapped_key_value, - even_key->wrapped_key_iv, even_key->content_iv}; - if (odd_key != nullptr) { - serializer_params.odd_key = {odd_key->entitlement_key_id, odd_key->key_id, - odd_key->wrapped_key_value, - odd_key->wrapped_key_iv, odd_key->content_iv}; + + // Process normal content keys. + std::vector content_keys = {*even_key}; + std::vector serializer_keys = { + &serializer_params.even_key}; + if (has_odd_key) { + content_keys.push_back(*odd_key); + serializer_keys.push_back(&serializer_params.odd_key); + } + Status status = WrapEntitledKeys(track_type, /*group_id=*/"", content_keys, + serializer_keys); + if (!status.ok()) { + return status; + } + + // Process group keys. + for (const auto& group_id : group_ids) { + EcmSerializerGroupKeyInfo group_key_info; + group_key_info.group_id = group_id; + std::vector group_serializer_keys = { + &group_key_info.even_key}; + if (has_odd_key) { + group_serializer_keys.push_back(&group_key_info.odd_key); + } + Status status = WrapEntitledKeys(track_type, group_id, content_keys, + group_serializer_keys); + if (!status.ok()) { + return status; + } + serializer_params.group_keys.push_back(group_key_info); } serializer_params.fingerprinting = fingerprinting_; @@ -175,12 +197,13 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const { - return GenerateEcm(key, nullptr, track_type, serialized_ecm); + return GenerateEcm(key, nullptr, track_type, group_ids, serialized_ecm); } -Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id, - uint8_t* continuity_counter, +Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, + uint8_t table_id, uint8_t* continuity_counter, absl::Span packet, ssize_t* bytes_modified) { if (ecm.empty()) { @@ -207,38 +230,49 @@ Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table return OkStatus(); } -Status Ecm::WrapEntitledKeys(const std::string& track_type, - const std::vector& keys) const { - if (!initialized_) { - return {error::INTERNAL, "Not initialized."}; - } - if (keys.empty()) { +Status Ecm::WrapEntitledKeys( + const std::string& track_type, const std::string& group_id, + const std::vector& content_keys, + std::vector serializer_keys) const { + DCHECK(initialized_); + if (content_keys.empty()) { return {error::INVALID_ARGUMENT, "Vector of EntitledKeyInfo is empty."}; } - auto ekey_map_entry = entitlement_keys_.find(track_type); + auto ekey_map_entry = + entitlement_keys_.find(std::make_pair(track_type, group_id)); if (ekey_map_entry == entitlement_keys_.end()) { return {error::INTERNAL, "No Entitlement Key found for given track_type."}; } + const std::vector& entitlement_keys = + ekey_map_entry->second; - const auto& ekey_list = ekey_map_entry->second; - if (ekey_list.size() != keys.size()) { + if (entitlement_keys.size() != content_keys.size()) { return {error::INTERNAL, - "Number of Entitled keys and Entitlement keys must match."}; + "Number of content keys and entitlement keys must match."}; + } + if (content_keys.size() != serializer_keys.size()) { + return {error::INTERNAL, + "Number of content keys and serializer keys must match."}; } - auto entitlement_key = ekey_list.begin(); - for (auto entitled_key : keys) { - entitled_key->entitlement_key_id = entitlement_key->key_id; - // Wrap key using entitlement key. - Status status = - WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv, - entitled_key->key_value, &entitled_key->wrapped_key_value); + for (int i = 0; i < entitlement_keys.size(); ++i) { + // Fill these fields only if this is NOT a group key. + if (group_id.empty()) { + serializer_keys[i]->wrapped_key_iv = content_keys[i].wrapped_key_iv; + serializer_keys[i]->content_iv = content_keys[i].content_iv; + serializer_keys[i]->content_key_id = content_keys[i].key_id; + } + + serializer_keys[i]->entitlement_key_id = entitlement_keys[i].key_id; + Status status = WrapKey( + entitlement_keys[i].key_value, content_keys[i].wrapped_key_iv, + content_keys[i].key_value, &serializer_keys[i]->wrapped_key_value); if (!status.ok()) { return status; } - entitlement_key++; } + return OkStatus(); } @@ -291,20 +325,12 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const { return OkStatus(); } -size_t Ecm::CountEntitlementKeys() const { - size_t count = 0; - for (const auto& track : entitlement_keys_) { - count += track.second.size(); - } - return count; -} - bool Ecm::CheckEntitlementKeys() const { for (const auto& track : entitlement_keys_) { - if (track.second.size() < (paired_keys_required_ ? 2 : 1)) { - LOG(ERROR) << " Wrong number of entitlement keys for track " - << track.first << ": " << (paired_keys_required_ ? 2 : 1) - << " expected, " << track.second.size() << " received."; + if (track.second.size() != (paired_keys_required_ ? 2 : 1)) { + LOG(ERROR) << " Wrong number of entitlement keys: " + << (paired_keys_required_ ? 2 : 1) << " expected, " + << track.second.size() << " received."; return false; } } diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index 34a4d20..4d53f24 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -10,6 +10,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_ #include +#include #include #include #include @@ -111,6 +112,8 @@ class Ecm { // |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. + // |group_ids| If specified, the content keys will be additionally encrypted + // by group entitlement keys with specified |group_ids|. // |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. @@ -119,6 +122,7 @@ class Ecm { virtual Status GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const; // Accept a key and IV and construct an ECM that will fit into a Transport @@ -127,11 +131,14 @@ class Ecm { // Args: // |key| information for key to be encoded into ECM. // |track_type| the track that the key is being used to encrypt. + // |group_ids| If specified, the content keys will be additionally encrypted + // by group entitlement keys with specified |group_ids|. // |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(EntitledKeyInfo* key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const; // Generate a TS packet with the given |ecm| as payload. @@ -165,16 +172,6 @@ class Ecm { ecm_serializer_ = std::move(ecm_serializer); } - // Apply the Entitlement key with the given track type and polarity - // (even vs. odd key) to wrap the given Entitled key. - // Args: - // |track_type| the track type for the keys. The type_track must match one - // of the |EcmInitParameters::track_types| strings passed into Initialize(). - // |key| the Entitled Key to be wrapped. - virtual Status WrapEntitledKeys( - const std::string& track_type, - const std::vector& keys) const; - private: // 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 @@ -184,32 +181,23 @@ class Ecm { std::string key_value; }; - // Check whether we've acquired entitlement keys. - bool HaveEntitlementKeys() const { return !entitlement_keys_.empty(); } - - // Count the different tracks for which we have entitlement keys. - size_t CountEntitlementTracks() const { return entitlement_keys_.size(); } - - // Count the total number of entitlement keys in our current state. - size_t CountEntitlementKeys() const; - // Verify that each track has the right number of keys. Complain if not. bool CheckEntitlementKeys() const; - // Remove all current entitlement keys. - void ClearEntitlementKeys() { entitlement_keys_.clear(); } - - // 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 EntitlementKeyIdValue& key) { - auto emplaced = entitlement_keys_.emplace( - track_type, std::list{}); - if (is_even_key) { - emplaced.first->second.push_front(key); - } else { - emplaced.first->second.push_back(key); - } - } + // Apply the entitlement keys with the given track type (and group_ids if + // specified) to wrap the given |keys|. + // Args: + // |track_type| the track type for the entitlement keys that should be used. + // |group_id| group id of the entitlement keys to use. Can be empty. + // |keys| the content keys to be wrapped. + // |serializer_keys| is the output vector. It must have the same size as + // |keys|. Fields in EcmSerializerKeyInfo will be updated. If group_id is + // non-empty, only fields entitlement_key_id and wrapped_key_value will be + // updated. + Status WrapEntitledKeys( + const std::string& track_type, const std::string& group_id, + const std::vector& content_keys, + std::vector serializer_keys) const; // Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|. // Returns the resulting wrapped key in |wrapped_key|. @@ -236,8 +224,11 @@ class Ecm { std::string ecc_private_signing_key_; // Entitlement keys needed for ECM generation. // The keys are added when the CasEncryptionResponse is processed. - // Maps from track_type to one/two EntitlementKeyIdValue with even key first. - std::map> entitlement_keys_; + // Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even + // key first. + std::map, + std::vector> + entitlement_keys_; std::unique_ptr ecm_serializer_; mutable absl::Mutex ecm_params_mutex_; diff --git a/media_cas_packager_sdk/internal/ecm_generator.cc b/media_cas_packager_sdk/internal/ecm_generator.cc deleted file mode 100644 index a58d82b..0000000 --- a/media_cas_packager_sdk/internal/ecm_generator.cc +++ /dev/null @@ -1,138 +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. -//////////////////////////////////////////////////////////////////////////////// - -#include "media_cas_packager_sdk/internal/ecm_generator.h" - -#include "glog/logging.h" - -namespace widevine { -namespace cas { - -static constexpr int kKeyDataSize = 16; -static constexpr int kKeyIvSize8 = 8; -static constexpr int kKeyIvSize16 = 16; -static constexpr int kMaxBytesKeyIdField = 16; - -std::string EcmGenerator::GenerateEcm(const EcmParameters& params) { - std::vector keys; - Status status = ProcessEcmParameters(params, &keys); - if (!status.ok() || !initialized_) { - LOG(ERROR) << " EcmParameters is not set up properly: " << status; - return ""; - } - std::string serialized_ecm; - std::string track_type = params.track_type; - if (params.rotation_enabled) { - status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm); - } else { - status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm); - } - if (!status.ok()) { - LOG(ERROR) << " Generate ECM call failed: " << status; - return ""; - } - return serialized_ecm; -} - -Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params, - std::vector* keys) { - initialized_ = false; - rotation_enabled_ = ecm_params.rotation_enabled; - - // Validate and add key data - keys->clear(); - uint32_t keys_needed = ecm_params.rotation_enabled ? 2 : 1; - if (ecm_params.key_params.size() < keys_needed) { - return {error::INVALID_ARGUMENT, - "Number of supplied keys is wrong (check rotation periods)."}; - } - // If content_iv_size_ is zero, use the size of first content IV as the - // expected size for all future IVs in this stream. - if (content_iv_size_ == 0) { - content_iv_size_ = ecm_params.key_params[0].content_iv.size(); - } - for (int i = 0; i < keys_needed; i++) { - Status status = ValidateKeyParameters(ecm_params.key_params[i]); - if (!status.ok()) { - return status; - } - keys->emplace_back(EntitledKeyInfo()); - EntitledKeyInfo& key = keys->back(); - key.key_id = ecm_params.key_params[i].key_id; - key.key_value = ecm_params.key_params[i].key_data; - key.wrapped_key_iv = ecm_params.key_params[i].wrapped_key_iv; - key.content_iv = ecm_params.key_params[i].content_iv; - } - current_key_index_ = 0; - current_key_even_ = true; - initialized_ = true; - return OkStatus(); -} - -Status EcmGenerator::ValidateKeyId(const std::string& id) const { - if (id.empty()) { - return {error::INVALID_ARGUMENT, "Key id is empty."}; - } - if (id.size() > kMaxBytesKeyIdField) { - return {error::INVALID_ARGUMENT, "Key id is too long."}; - } - return OkStatus(); -} - -Status EcmGenerator::ValidateKeyData(const std::string& key_data) const { - if (key_data.empty()) { - return {error::INVALID_ARGUMENT, "Key data is empty."}; - } - if (key_data.size() != kKeyDataSize) { - return {error::INVALID_ARGUMENT, "Key data is wrong size."}; - } - return OkStatus(); -} - -Status EcmGenerator::ValidateIv(const std::string& iv, - size_t required_size) const { - if (iv.empty()) { - return {error::INVALID_ARGUMENT, "IV is empty."}; - } - if (required_size != 8 && required_size != 16) { - return {error::INTERNAL, "IV size has not been set up correctly."}; - } - - if (iv.size() != required_size) { - return {error::INVALID_ARGUMENT, "IV has wrong or inconsistent size."}; - } - return OkStatus(); -} - -Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) const { - // All wrapped key IVs must be 16 bytes. - Status status = ValidateIv(iv, kIvSize16); - if (!status.ok()) { - LOG(ERROR) << " Wrapped key IV is not valid: " << status; - } - return status; -} - -Status EcmGenerator::ValidateContentIv(const std::string& iv) const { - Status status = ValidateIv(iv, content_iv_size_); - if (!status.ok()) { - LOG(ERROR) << " Content IV is not valid: " << status; - } - return status; -} - -Status EcmGenerator::ValidateKeyParameters( - const KeyParameters& key_params) const { - Status status = ValidateKeyId(key_params.key_id); - if (!status.ok()) return status; - - return ValidateContentIv(key_params.content_iv); -} - -} // namespace cas -} // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecm_generator.h b/media_cas_packager_sdk/internal/ecm_generator.h deleted file mode 100644 index 3ad0a5b..0000000 --- a/media_cas_packager_sdk/internal/ecm_generator.h +++ /dev/null @@ -1,92 +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_ECM_GENERATOR_H_ -#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_ - -#include - -#include -#include -#include -#include - -#include -#include "common/status.h" -#include "media_cas_packager_sdk/internal/ecm.h" - -namespace widevine { -namespace cas { - -// KeyParameters carries key information for a single encryption key. -// Instances of this struct are owned by an EcmParameters struct. -struct KeyParameters { - std::string key_id; - std::string key_data; - std::string wrapped_key_iv; - std::string content_iv; -}; - -// EcmParameters holds information that is needed by the EcmGenerator. It is -// partially set up with data from a KeyGenerator (obtained via GenerateKey()). -// IVs are added later. -struct EcmParameters { - // TODO(user): entitlement_key_id does not seem to be used, but assumed - // to exist by unit tests. - std::string entitlement_key_id; - bool rotation_enabled = true; - // TODO(user): rotation_periods does not seem to be used, but assumed - // to exist by unit tests. - uint32_t rotation_periods; - std::string track_type; - // TODO(user): Consider changing the vector to just two variables, - // one for even key, the other for odd key. - std::vector key_params; -}; - -// ECM Generator for Widevine/MediaCAS entitled keys. -class EcmGenerator { - public: - EcmGenerator() = default; - EcmGenerator(const EcmGenerator&) = delete; - EcmGenerator& operator=(const EcmGenerator&) = delete; - virtual ~EcmGenerator() = default; - - virtual std::string GenerateEcm(const EcmParameters& params); - - // Query the state of this ECM Generator - bool initialized() const { return initialized_; } - bool rotation_enabled() const { return rotation_enabled_; } - - void set_ecm(std::unique_ptr ecm) { ecm_ = std::move(ecm); } - - private: - friend class EcmGeneratorTest; - - Status ProcessEcmParameters(const EcmParameters& ecm_params, - std::vector* keys); - - Status ValidateKeyId(const std::string& id) const; - Status ValidateKeyData(const std::string& key_data) const; - Status ValidateWrappedKeyIv(const std::string& iv) const; - Status ValidateIv(const std::string& iv, size_t required_size) const; - Status ValidateContentIv(const std::string& iv) const; - Status ValidateKeyParameters(const KeyParameters& key_params) const; - - bool initialized_ = false; - bool rotation_enabled_ = false; - uint32_t current_key_index_ = 0; - bool current_key_even_ = true; - uint32_t content_iv_size_ = 0; - std::unique_ptr ecm_; -}; - -} // namespace cas -} // namespace widevine - -#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_ diff --git a/media_cas_packager_sdk/internal/ecm_generator_test.cc b/media_cas_packager_sdk/internal/ecm_generator_test.cc deleted file mode 100644 index f7163c4..0000000 --- a/media_cas_packager_sdk/internal/ecm_generator_test.cc +++ /dev/null @@ -1,351 +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. -//////////////////////////////////////////////////////////////////////////////// - -#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 "absl/strings/string_view.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 { -namespace cas { - -namespace { - -constexpr char kCADescriptor[] = "TestCa"; -constexpr char kEcm[] = "TestEcm"; -constexpr char kProvider[] = "Gfiber"; -constexpr char kContentId[] = "TestContent"; -constexpr int kDefaultKeyRotationPeriodMilliseconds = 30 * 60 * 1000; -constexpr char kPsshData[] = "TestPsshData"; - -constexpr char kEntitlementKeySingle[] = "testEKId12345678"; -constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh"; - -constexpr char kEcmKeyIdSingle[] = "key-id-One123456"; -constexpr char kEcmKeyDataSingle[] = "0123456701234567"; -constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh"; -constexpr char kEcmContentIvSingle[] = "ABCDEFGH"; - -constexpr char kEcmKeyIdEven[] = "key-Id-One123456"; -constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee"; -constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh"; -constexpr char kEcmContentIvEven[] = "ABCDEFGH"; - -constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789"; -constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO"; -constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4"; -constexpr char kEcmContentIvOdd[] = "AaCbEcGd"; - -constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id....."; -constexpr char kFakeCasEncryptionResponseKeyData[] = - "fakefakefakefakefakefakefakefake"; -constexpr char kFakeCasEncryptionResponseGroupKeyId[] = "fake_key_id12345"; -constexpr char kFakeCasEncryptionResponseGroupKeyData[] = - "fakefakefakefakefakefake12345678"; - -constexpr char kTrackType[] = "SD"; - -void SetKeyInfoInTestResponse( - widevine::CasEncryptionResponse_KeyInfo* key, bool group_id_exists) { - ASSERT_TRUE(key != nullptr); - if (!group_id_exists) { - key->set_key_id(kFakeCasEncryptionResponseKeyId); - key->set_key(kFakeCasEncryptionResponseKeyData); - } else { - key->set_key_id(kFakeCasEncryptionResponseGroupKeyId); - key->set_key(kFakeCasEncryptionResponseGroupKeyData); - } -} - -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; - 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); - response.set_content_id(request.content_id()); - response.set_group_id(request.group_id()); - for (const auto& track_type : request.track_types()) { - if (request.key_rotation()) { - // Add the Even key. - auto key = response.add_entitlement_keys(); - SetKeyInfoInTestResponse(key, request.has_group_id()); - key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); - // Add the Odd key. - key = response.add_entitlement_keys(); - SetKeyInfoInTestResponse(key, request.has_group_id()); - key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); - } else { - auto key = response.add_entitlement_keys(); - SetKeyInfoInTestResponse(key, request.has_group_id()); - 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(); -} - -} // namespace - -class EcmGeneratorTest : public testing::Test { - protected: - void SetUp() override {} - - Status ProcessEcmParameters(const EcmParameters& params, - std::vector* keys) { - return ecm_gen_.ProcessEcmParameters(params, keys); - } - - int EntitlementKeySize(const EcmParameters& params) const { - return params.entitlement_key_id.size(); - } - - void SetTestConfig1(EcmParameters* params) { - params->entitlement_key_id = kEntitlementKeySingle; - params->rotation_enabled = false; - params->key_params.push_back(kKeyParamsSingle); - params->track_type = kTrackType; - } - - void SetTestConfig2(EcmParameters* params) { - KeyParameters even_key_params; - KeyParameters odd_key_params; - params->entitlement_key_id = kEntitlementKeyDouble; - params->rotation_enabled = true; - params->rotation_periods = 2; - params->key_params.push_back(kKeyParamsEven); - params->key_params.push_back(kKeyParamsOdd); - params->track_type = kTrackType; - } - - // Call this to setup the guts (Ecm) of the ECM Generator. - void PrepareEcmGenerator(bool key_rotation_enabled, - const std::string& group_id) { - std::string entitlement_request; - std::string entitlement_response; - ecm_init_params_.key_rotation_enabled = key_rotation_enabled; - FixedKeyFetcher key_fetcher; - EntitlementRequestParams request_params; - request_params.content_id = kContentId; - request_params.content_provider = kProvider; - request_params.group_id = group_id; - 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)); - 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_)); - } - - void CheckECMOutput(absl::string_view ecm_string, - absl::string_view response_entitlement_key_id, - absl::string_view response_entitltement_key_data, - bool key_rotation_enabled) { - // Expected size (bytes): - // CA system ID: 2 bytes - // version: 1 byte - // flags: 1 byte - // flags: 1 byte - // entitlement key ID: 16 bytes - // Single key: ID (16), Data (16), IV (16), IV (8) = 56 - // total = 77 (ECM message complete if no key retotation) - // second entitlement key ID: 16 bytes - // Second key: ID (16), Data (16), IV (16), IV (8) = 56 - // total = 149 - uint32_t ecm_string_size = key_rotation_enabled ? 149 : 77; - ASSERT_EQ(ecm_string_size, ecm_string.size()); - EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte. - EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte. - EXPECT_EQ('\x02', ecm_string[2]); // ECM version - if (key_rotation_enabled) { - EXPECT_EQ('\x03', ecm_string[3]); // flags - } else { - EXPECT_EQ('\x02', ecm_string[3]); // flags - } - EXPECT_EQ('\x80', ecm_string[4]); // flags - EXPECT_EQ(response_entitlement_key_id, ecm_string.substr(5, 16)); - if (!key_rotation_enabled) { - EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16)); - // Key data has been wrapped (encrypted). - EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16)); - EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16)); - // Unwrap key and compare with original. - absl::string_view wrapping_key = response_entitltement_key_data; - absl::string_view wrapping_iv = ecm_string.substr(53, 16); - absl::string_view wrapped_key = ecm_string.substr(37, 16); - std::string unwrapped_key = crypto_util::DecryptAesCbcNoPad( - std::string(wrapping_key), std::string(wrapping_iv), - std::string(wrapped_key)); - EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key); - EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8)); - } else { - EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16)); - // Key data has been wrapped (encrypted). - EXPECT_NE(kEcmKeyDataEven, ecm_string.substr(37, 16)); - EXPECT_EQ(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16)); - EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8)); - EXPECT_EQ(response_entitlement_key_id, ecm_string.substr(77, 16)); - EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16)); - // Key data has been wrapped (encrypted). - EXPECT_NE(kEcmKeyDataOdd, ecm_string.substr(109, 16)); - EXPECT_EQ(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16)); - EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8)); - // Unwrap even key and compare with original. - absl::string_view wrapping_key_even = response_entitltement_key_data; - absl::string_view wrapping_iv_even = ecm_string.substr(53, 16); - absl::string_view wrapped_key_even = ecm_string.substr(37, 16); - std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad( - std::string(wrapping_key_even), std::string(wrapping_iv_even), - std::string(wrapped_key_even)); - EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even); - // Unwrap odd key and compare with original. - absl::string_view wrapping_key_odd = response_entitltement_key_data; - absl::string_view wrapping_iv_odd = ecm_string.substr(125, 16); - absl::string_view wrapped_key_odd = ecm_string.substr(109, 16); - std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad( - std::string(wrapping_key_odd), std::string(wrapping_iv_odd), - std::string(wrapped_key_odd)); - EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd); - } - } - - const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle, - kEcmKeyDataSingle, - kEcmWrappedKeyIvSingle, - {kEcmContentIvSingle}}; - const KeyParameters kKeyParamsEven{kEcmKeyIdEven, - kEcmKeyDataEven, - kEcmWrappedKeyIvEven, - {kEcmContentIvEven}}; - const KeyParameters kKeyParamsOdd{ - kEcmKeyIdOdd, kEcmKeyDataOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}}; - - std::unique_ptr ecm_; - EcmInitParameters ecm_init_params_; - EcmGenerator ecm_gen_; -}; - -TEST_F(EcmGeneratorTest, InitializeNoRotation) { - EXPECT_FALSE(ecm_gen_.initialized()); - PrepareEcmGenerator(false, /*group_id=*/""); - EcmParameters ecm_params; - std::vector keys; - SetTestConfig1(&ecm_params); - - Status status = ProcessEcmParameters(ecm_params, &keys); - ASSERT_OK(status); - ASSERT_EQ(EntitlementKeySize(ecm_params), 16); - ASSERT_TRUE(ecm_gen_.initialized()); - EXPECT_FALSE(ecm_gen_.rotation_enabled()); -} - -TEST_F(EcmGeneratorTest, GenerateNoRotation) { - PrepareEcmGenerator(false, /*group_id=*/""); - EcmParameters ecm_params; - SetTestConfig1(&ecm_params); - std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); - - CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId, - kFakeCasEncryptionResponseKeyData, false); -} - -TEST_F(EcmGeneratorTest, GenerateNoRotationWithGroupId) { - PrepareEcmGenerator(false, "groupId_1"); - EcmParameters ecm_params; - SetTestConfig1(&ecm_params); - std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); - CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId, - kFakeCasEncryptionResponseGroupKeyData, false); -} - -TEST_F(EcmGeneratorTest, Generate2NoRotation) { - PrepareEcmGenerator(false, /*group_id=*/""); - EcmParameters ecm_params; - SetTestConfig1(&ecm_params); - std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); - ecm_string = ecm_gen_.GenerateEcm(ecm_params); - CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId, - kFakeCasEncryptionResponseKeyData, false); -} - -TEST_F(EcmGeneratorTest, InitializeSimpleRotation) { - EXPECT_FALSE(ecm_gen_.initialized()); - EcmParameters ecm_params; - std::vector keys; - PrepareEcmGenerator(true, /*group_id=*/""); - SetTestConfig2(&ecm_params); - ecm_init_params_.key_rotation_enabled = true; - Status status = ProcessEcmParameters(ecm_params, &keys); - EXPECT_TRUE(status.ok()); - EXPECT_TRUE(ecm_gen_.initialized()); - EXPECT_TRUE(ecm_gen_.rotation_enabled()); -} - -TEST_F(EcmGeneratorTest, GenerateSimpleRotation) { - EcmParameters ecm_params; - PrepareEcmGenerator(true, /*group_id=*/""); - SetTestConfig2(&ecm_params); - std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); - CheckECMOutput(ecm_string, kFakeCasEncryptionResponseKeyId, - kFakeCasEncryptionResponseKeyData, true); -} - -TEST_F(EcmGeneratorTest, GenerateSimpleRotationWithGroupId) { - EcmParameters ecm_params; - PrepareEcmGenerator(true, "groupId_1"); - SetTestConfig2(&ecm_params); - std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); - CheckECMOutput(ecm_string, kFakeCasEncryptionResponseGroupKeyId, - kFakeCasEncryptionResponseGroupKeyData, true); -} - -} // namespace cas -} // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecm_serializer.h b/media_cas_packager_sdk/internal/ecm_serializer.h index 0309080..d1e3060 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer.h +++ b/media_cas_packager_sdk/internal/ecm_serializer.h @@ -11,6 +11,7 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_ +#include #include #include @@ -32,6 +33,13 @@ struct EcmSerializerKeyInfo { std::string content_iv; }; +// Group key information used to generate serialized ecm. +struct EcmSerializerGroupKeyInfo { + std::string group_id; + EcmSerializerKeyInfo even_key; + EcmSerializerKeyInfo odd_key; +}; + // Parameters passed into ECM serializer to generate ECM. struct EcmSerializerParams { uint16_t cas_id; @@ -44,6 +52,7 @@ struct EcmSerializerParams { // Private signing key used to sign ECM data. Must be an elliptic-curve // cryptography key. std::string ecc_private_signing_key; + std::vector group_keys; }; enum class EcmSerializerVersion { kV2 = 0, kV3 }; diff --git a/media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc b/media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc index 296b60c..0b17e76 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc +++ b/media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "media_cas_packager_sdk/internal/ecm_serializer_v2.h" +#include #include #include diff --git a/media_cas_packager_sdk/internal/ecm_serializer_v3.cc b/media_cas_packager_sdk/internal/ecm_serializer_v3.cc index 16587e1..5f31dd3 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer_v3.cc +++ b/media_cas_packager_sdk/internal/ecm_serializer_v3.cc @@ -8,8 +8,11 @@ #include "media_cas_packager_sdk/internal/ecm_serializer_v3.h" #include +#include +#include #include "glog/logging.h" +#include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" @@ -67,6 +70,57 @@ Status ValidateKeyInfo(const EcmSerializerKeyInfo& key_info, bool is_even_key) { return OkStatus(); } +// Fields entitlement_key_id and wrapped_key_value must exist; +// wrapped_key_iv is optional; content_iv is ignored as it should be the +// same as the one specified in normal key. +Status ValidateGroupSerializerKeyInfo(EcmSerializerKeyInfo key_info) { + if (key_info.entitlement_key_id.size() != kKeyIdSizeBytes) { + return {error::INVALID_ARGUMENT, + absl::StrCat("Unexpected group entitlement key ID size: ", + key_info.entitlement_key_id.size())}; + } + if (key_info.wrapped_key_value.size() != kKeyDataSizeBytes) { + return {error::INVALID_ARGUMENT, + absl::StrCat("Unexpected group wrapped key size: ", + key_info.wrapped_key_value.size())}; + } + // Wrapped key IV may be empty. + if (!key_info.wrapped_key_iv.empty() && + key_info.wrapped_key_iv.size() != kWrappedKeyIvSizeBytes) { + return {error::INVALID_ARGUMENT, + absl::StrCat("Unexpected group wrapped key iv size: ", + key_info.wrapped_key_iv.size())}; + } + return OkStatus(); +} + +Status ValidateGroupKey( + const std::vector& group_keys, + bool has_odd_key) { + // Used to make sure group ids are unique. + absl::flat_hash_set group_ids; + for (const auto& group_key : group_keys) { + if (group_key.group_id.empty()) { + return {error::INVALID_ARGUMENT, "Missing group id."}; + } + if (!group_ids.insert(group_key.group_id).second) { + return {error::INVALID_ARGUMENT, "Group ids are not unique."}; + } + + Status status = ValidateGroupSerializerKeyInfo(group_key.even_key); + if (!status.ok()) { + return status; + } + if (has_odd_key) { + status = ValidateGroupSerializerKeyInfo(group_key.odd_key); + if (!status.ok()) { + return status; + } + } + } + return OkStatus(); +} + EcmMetaData::CipherMode ConvertCryptoModeToProtoCipherMode( CryptoMode crypto_mode) { switch (crypto_mode) { @@ -114,6 +168,11 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params, return {error::INVALID_ARGUMENT, "Content IV size must match."}; } } + status = ValidateGroupKey(params.group_keys, has_odd_key); + if (!status.ok()) { + LOG(ERROR) << "Invalid group key info"; + return status; + } EcmPayload ecm_payload; EcmMetaData* meta_data = ecm_payload.mutable_meta_data(); @@ -162,6 +221,27 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params, } } + for (const auto& group_key : params.group_keys) { + EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id(group_key.group_id); + EcmKeyData* even_key_data = group_key_data->mutable_even_key_data(); + even_key_data->set_entitlement_key_id( + group_key.even_key.entitlement_key_id); + even_key_data->set_wrapped_key_data(group_key.even_key.wrapped_key_value); + if (!group_key.even_key.wrapped_key_iv.empty()) { + even_key_data->set_wrapped_key_iv(group_key.even_key.wrapped_key_iv); + } + if (has_odd_key) { + EcmKeyData* odd_key_data = group_key_data->mutable_odd_key_data(); + odd_key_data->set_entitlement_key_id( + group_key.odd_key.entitlement_key_id); + odd_key_data->set_wrapped_key_data(group_key.odd_key.wrapped_key_value); + if (!group_key.odd_key.wrapped_key_iv.empty()) { + odd_key_data->set_wrapped_key_iv(group_key.odd_key.wrapped_key_iv); + } + } + } + const std::string serialized_ecm_payload = ecm_payload.SerializeAsString(); SignedEcmPayload signed_ecm_payload; signed_ecm_payload.set_serialized_payload(serialized_ecm_payload); diff --git a/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc b/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc index f36e528..0c8cb17 100644 --- a/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc +++ b/media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "media_cas_packager_sdk/internal/ecm_serializer_v3.h" +#include #include #include @@ -86,6 +87,35 @@ class EcmSerializerV3Test : public testing::Test { EXPECT_EQ(ecm_key_data.wrapped_key_iv(), key_info.wrapped_key_iv); } + void AddGroupKey(const std::string& group_id, + EcmGroupKeyData* expected_group_key_data) { + EcmSerializerGroupKeyInfo group_key; + group_key.group_id = group_id; + group_key.even_key.entitlement_key_id = kEntitlementId; + group_key.even_key.wrapped_key_value = kWrappedContentKey; + group_key.even_key.wrapped_key_iv = kWrappedKeyIv; + group_key.odd_key.entitlement_key_id = kEntitlementId2; + group_key.odd_key.wrapped_key_value = kWrappedContentKey2; + group_key.odd_key.wrapped_key_iv = kWrappedKeyIv2; + serializer_params_.group_keys.push_back(group_key); + + if (expected_group_key_data != nullptr) { + expected_group_key_data->set_group_id(group_id); + expected_group_key_data->mutable_even_key_data()->set_entitlement_key_id( + kEntitlementId); + expected_group_key_data->mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey); + expected_group_key_data->mutable_even_key_data()->set_wrapped_key_iv( + kWrappedKeyIv); + expected_group_key_data->mutable_odd_key_data()->set_entitlement_key_id( + kEntitlementId2); + expected_group_key_data->mutable_odd_key_data()->set_wrapped_key_data( + kWrappedContentKey2); + expected_group_key_data->mutable_odd_key_data()->set_wrapped_key_iv( + kWrappedKeyIv2); + } + } + EcmSerializerParams serializer_params_; }; @@ -407,6 +437,142 @@ TEST_F(EcmSerializerV3Test, GenerateSignatureSuccess) { signed_payload.signature())); } +TEST_F(EcmSerializerV3Test, GroupKeySuccess) { + EcmSerializerV3 ecm_serializer; + EcmGroupKeyData expected_group_key_data_1; + EcmGroupKeyData expected_group_key_data_2; + AddGroupKey("group_id_1", &expected_group_key_data_1); + AddGroupKey("group_id_2", &expected_group_key_data_2); + std::string serialized_ecm; + + ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm)); + + SignedEcmPayload signed_payload; + ASSERT_TRUE(signed_payload.ParseFromString( + serialized_ecm.substr(kEcmProtoStartOffset))); + EcmPayload payload; + ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload())); + ASSERT_EQ(payload.group_key_data_size(), 2); + EXPECT_EQ(payload.group_key_data(0).SerializeAsString(), + expected_group_key_data_1.SerializeAsString()); + EXPECT_EQ(payload.group_key_data(1).SerializeAsString(), + expected_group_key_data_2.SerializeAsString()); +} + +TEST_F(EcmSerializerV3Test, GroupKeyEvenKeyOnlySuccess) { + EcmSerializerV3 ecm_serializer; + EcmSerializerKeyInfo empty_odd_key; + serializer_params_.odd_key = empty_odd_key; + EcmSerializerGroupKeyInfo group_key; + group_key.group_id = "group_id"; + group_key.even_key.entitlement_key_id = kEntitlementId; + group_key.even_key.wrapped_key_value = kWrappedContentKey; + group_key.even_key.wrapped_key_iv = kWrappedKeyIv; + serializer_params_.group_keys.push_back(group_key); + EcmGroupKeyData expected_group_key_data; + expected_group_key_data.set_group_id("group_id"); + expected_group_key_data.mutable_even_key_data()->set_entitlement_key_id( + kEntitlementId); + expected_group_key_data.mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey); + expected_group_key_data.mutable_even_key_data()->set_wrapped_key_iv( + kWrappedKeyIv); + std::string serialized_ecm; + + ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm)); + + SignedEcmPayload signed_payload; + ASSERT_TRUE(signed_payload.ParseFromString( + serialized_ecm.substr(kEcmProtoStartOffset))); + EcmPayload payload; + ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload())); + ASSERT_EQ(payload.group_key_data_size(), 1); + EXPECT_EQ(payload.group_key_data(0).SerializeAsString(), + expected_group_key_data.SerializeAsString()); +} + +TEST_F(EcmSerializerV3Test, GroupKeyMissingOddKeyFail) { + EcmSerializerV3 ecm_serializer; + EcmSerializerGroupKeyInfo group_key; + group_key.group_id = "group_id"; + group_key.even_key.entitlement_key_id = kEntitlementId; + group_key.even_key.wrapped_key_value = kWrappedContentKey; + group_key.even_key.wrapped_key_iv = kWrappedKeyIv; + serializer_params_.group_keys.push_back(group_key); + std::string serialized_ecm; + + ASSERT_FALSE( + ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok()); +} + +TEST_F(EcmSerializerV3Test, GroupKeyMissingEvenKeyFail) { + EcmSerializerV3 ecm_serializer; + EcmSerializerGroupKeyInfo group_key; + group_key.group_id = "group_id"; + group_key.odd_key.entitlement_key_id = kEntitlementId; + group_key.odd_key.wrapped_key_value = kWrappedContentKey; + group_key.odd_key.wrapped_key_iv = kWrappedKeyIv; + serializer_params_.group_keys.push_back(group_key); + std::string serialized_ecm; + + ASSERT_FALSE( + ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok()); +} + +TEST_F(EcmSerializerV3Test, GroupKeyRepeatedGroupIdFail) { + EcmSerializerV3 ecm_serializer; + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + std::string serialized_ecm; + + ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm), + Status(error::INVALID_ARGUMENT, "Group ids are not unique.")); +} + +TEST_F(EcmSerializerV3Test, GroupKeyNoGroupIdFail) { + EcmSerializerV3 ecm_serializer; + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + AddGroupKey(/*group_id=*/"", /*expected_group_key_data=*/nullptr); + std::string serialized_ecm; + + ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm), + Status(error::INVALID_ARGUMENT, "Missing group id.")); +} + +TEST_F(EcmSerializerV3Test, GroupKeyInvalidEntitlementKeyIdFail) { + EcmSerializerV3 ecm_serializer; + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + serializer_params_.group_keys[0].even_key.entitlement_key_id = "short_id"; + std::string serialized_ecm; + + ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm), + Status(error::INVALID_ARGUMENT, + "Unexpected group entitlement key ID size: 8")); +} + +TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyValueFail) { + EcmSerializerV3 ecm_serializer; + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + serializer_params_.group_keys[0].odd_key.wrapped_key_value = "short_v"; + std::string serialized_ecm; + + ASSERT_EQ( + ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm), + Status(error::INVALID_ARGUMENT, "Unexpected group wrapped key size: 7")); +} + +TEST_F(EcmSerializerV3Test, GroupKeyInvalidWrappedKeyIvFail) { + EcmSerializerV3 ecm_serializer; + AddGroupKey("group_id", /*expected_group_key_data=*/nullptr); + AddGroupKey("group_id2", /*expected_group_key_data=*/nullptr); + serializer_params_.group_keys[1].odd_key.wrapped_key_iv = "short_iv"; + std::string serialized_ecm; + + ASSERT_EQ(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm), + Status(error::INVALID_ARGUMENT, + "Unexpected group wrapped key iv size: 8")); +} + } // namespace } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecm_test.cc b/media_cas_packager_sdk/internal/ecm_test.cc index ffb81ee..edcce9a 100644 --- a/media_cas_packager_sdk/internal/ecm_test.cc +++ b/media_cas_packager_sdk/internal/ecm_test.cc @@ -8,6 +8,10 @@ #include "media_cas_packager_sdk/internal/ecm.h" +#include +#include +#include + #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/memory/memory.h" @@ -32,6 +36,7 @@ constexpr char kTrackTypeSD[] = "SD"; constexpr char kTrackTypeHD[] = "HD"; constexpr char kWrappedKeyIv[] = "wrapped_key_iv.."; constexpr char kEntitlementKeyId[] = "entitlement_Mock"; +constexpr char kEntitlementKeyValue[] = "key__value.key__value.key__value"; constexpr char kWrappedKeyValue[] = "MockMockMockMock"; constexpr size_t kEcmVersionIndex = 2; @@ -96,9 +101,11 @@ class EcmTest : public testing::Test { params_one_key_.key_rotation_enabled = false; params_two_keys_.key_rotation_enabled = true; - injected_entitlement_one_ = {kTrackTypeSD, true, "entitlement_id_1", + injected_entitlement_one_ = {kTrackTypeSD, /*group_id=*/"", + /*is_even_key=*/true, "entitlement_id_1", "key__value.key__value.key__value"}; - injected_entitlement_two_ = {kTrackTypeSD, true, "entitlement_id_2", + injected_entitlement_two_ = {kTrackTypeSD, /*group_id=*/"", + /*is_even_key=*/false, "entitlement_id_2", "key__value.key__value.key__value"}; } @@ -121,7 +128,9 @@ TEST_F(EcmTest, GenerateEcmNotInitialized) { EntitledKeyInfo key1; std::string ecm_data; EXPECT_EQ(error::INTERNAL, - ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data) + ecm_gen + .GenerateSingleKeyEcm(&key1, kTrackTypeSD, /*group_ids=*/{}, + &ecm_data) .error_code()); } @@ -130,9 +139,10 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) { EntitledKeyInfo key1; EntitledKeyInfo key2; std::string ecm_data; - EXPECT_EQ( - error::INTERNAL, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code()); + EXPECT_EQ(error::INTERNAL, ecm_gen + .GenerateEcm(&key1, &key2, kTrackTypeSD, + /*group_ids=*/{}, &ecm_data) + .error_code()); } TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) { @@ -179,7 +189,8 @@ TEST_F(EcmTest, InitIvSize16x16OK) { } class EcmInitWithValidCasIdTest : public EcmTest, - public testing::WithParamInterface {}; + public testing::WithParamInterface { +}; TEST_P(EcmInitWithValidCasIdTest, InitWithValidCasIdSucceed) { params_simple_.cas_id = GetParam(); Ecm ecm_gen; @@ -191,9 +202,9 @@ INSTANTIATE_TEST_SUITE_P(EcmInitWithValidCasIdTest, EcmInitWithValidCasIdTest, kWidevineNewSystemIdLowerBound, 0x56C5, kWidevineNewSystemIdUpperBound)); -class EcmInitWithInvalidCasIdTest : public EcmTest, - public testing::WithParamInterface { -}; +class EcmInitWithInvalidCasIdTest + : public EcmTest, + public testing::WithParamInterface {}; TEST_P(EcmInitWithInvalidCasIdTest, InitWithInvalidCasIdFail) { params_simple_.cas_id = GetParam(); Ecm ecm_gen; @@ -219,8 +230,8 @@ TEST_P(EcmSerializerVersionTest, EcmSerializerOk) { ASSERT_OK(ecm_gen.Initialize(params, {injected_entitlement_one_})); std::string ecm; - ASSERT_OK( - ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD, &ecm)); + ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD, + /*group_ids=*/{}, &ecm)); ASSERT_GT(ecm.size(), kEcmVersionIndex); EXPECT_EQ(ecm[kEcmVersionIndex], params.ecm_version == EcmVersion::kV2 ? 2 : 3); @@ -237,8 +248,10 @@ TEST_F(EcmTest, GenerateWithBadTrackType) { EntitledKeyInfo key1 = valid1_iv_16_8_; EntitledKeyInfo key2 = valid2_iv_16_8_; std::string ecm; - EXPECT_EQ(error::INTERNAL, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code()); + EXPECT_EQ( + error::INTERNAL, + ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, /*group_ids=*/{}, &ecm) + .error_code()); } class MockEcmSerializer : public EcmSerializer { @@ -252,6 +265,7 @@ class MockEcmSerializer : public EcmSerializer { MOCK_METHOD(EcmSerializerVersion, Version, (), (const, override)); }; +// For easier test by setting mock ecm serializer and fixed wrapped key value. class FakeEcm : public Ecm { public: FakeEcm() = default; @@ -262,18 +276,16 @@ class FakeEcm : public Ecm { return Ecm::SetEcmSerializer(std::move(ecm_serializer)); } - Status WrapEntitledKeys( - const std::string& track_type, - const std::vector& keys) const override { - for (auto entitled_key : keys) { - entitled_key->entitlement_key_id = kEntitlementKeyId; - entitled_key->wrapped_key_value = kWrappedKeyValue; - } + Status WrapKey(const std::string& wrapping_key, + const std::string& wrapping_iv, const std::string& key_value, + std::string* wrapped_key) const override { + *wrapped_key = kWrappedKeyValue; return OkStatus(); } }; -bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) { +bool IsEcmSerializerKeyInfoEq(const EcmSerializerKeyInfo& a, + const EcmSerializerKeyInfo& b) { return (a.entitlement_key_id == b.entitlement_key_id) && (a.content_key_id == b.content_key_id) && (a.wrapped_key_value == b.wrapped_key_value) && @@ -281,12 +293,29 @@ bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) { (a.content_iv == b.content_iv); } +bool IsEcmSerializerGroupKeyInfoEq( + const std::vector& a, + const std::vector& b) { + if (a.size() != b.size()) { + return false; + } + for (size_t i = 0; i < a.size(); ++i) { + if (a[i].group_id != b[i].group_id || + !IsEcmSerializerKeyInfoEq(a[i].even_key, b[i].even_key) || + !IsEcmSerializerKeyInfoEq(a[i].odd_key, b[i].odd_key)) { + return false; + } + } + return true; +} + MATCHER_P(EcmSerializerParamsEq, expected, "") { return (arg.cas_id == expected.cas_id) && (arg.crypto_mode == expected.crypto_mode) && (arg.age_restriction == expected.age_restriction) && IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) && - IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key); + IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key) && + IsEcmSerializerGroupKeyInfoEq(arg.group_keys, expected.group_keys); } TEST_F(EcmTest, GenerateEcmTwoKeysOK) { @@ -302,10 +331,12 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) { expected_params.cas_id = kWvCasCaSystemId; expected_params.age_restriction = 0; expected_params.crypto_mode = CryptoMode::kAesCtr; - expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue, - key1.wrapped_key_iv, key1.content_iv}; - expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue, - key2.wrapped_key_iv, key2.content_iv}; + expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id, + kWrappedKeyValue, key1.wrapped_key_iv, + key1.content_iv}; + expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id, + kWrappedKeyValue, key2.wrapped_key_iv, + key2.content_iv}; std::string expected_serialized_ecm = "expected_serialized_ecm"; EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull())) @@ -313,8 +344,8 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) { DoAll(SetArgPointee<1>(expected_serialized_ecm), Return(OkStatus()))); std::string actual_generated_ecm; - ASSERT_OK( - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &actual_generated_ecm)); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, /*group_ids=*/{}, + &actual_generated_ecm)); EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm); } @@ -340,16 +371,19 @@ TEST_F(EcmTest, GenerateEcmParamsPassedDown) { expected_params.age_restriction = params.age_restriction; expected_params.crypto_mode = params.crypto_mode; expected_params.ecc_private_signing_key = params.ecc_private_signing_key; - expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue, - key1.wrapped_key_iv, key1.content_iv}; - expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue, - key2.wrapped_key_iv, key2.content_iv}; + expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id, + kWrappedKeyValue, key1.wrapped_key_iv, + key1.content_iv}; + expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id, + kWrappedKeyValue, key2.wrapped_key_iv, + key2.content_iv}; EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull())) .WillOnce(Return(OkStatus())); std::string generated_ecm; - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &generated_ecm)); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, /*group_ids=*/{}, + &generated_ecm)); } TEST_F(EcmTest, GenerateEcmSerializeErrorOK) { @@ -365,10 +399,88 @@ TEST_F(EcmTest, GenerateEcmSerializeErrorOK) { std::string ecm; EXPECT_EQ(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, - kTrackTypeSD, &ecm), + kTrackTypeSD, /*group_ids=*/{}, &ecm), expected_status); } +TEST_F(EcmTest, GenerateEcmWithGroupKeysSuccess) { + FakeEcm ecm_gen; + EcmInitParameters params; + std::vector group_ids = {"group_id_1", "group_id_2"}; + // Group even and odd entitlement keys for group_id_1. + EntitlementKeyInfo entitlement_3 = { + kTrackTypeSD, group_ids[0], + /*is_even_key=*/true, "entitlement_id_3", kEntitlementKeyValue, + }; + EntitlementKeyInfo entitlement_4 = { + kTrackTypeSD, group_ids[0], + /*is_even_key=*/false, "entitlement_id_4", kEntitlementKeyValue, + }; + // Group even and odd entitlement keys for group_id_2. + EntitlementKeyInfo entitlement_5 = { + kTrackTypeSD, group_ids[1], + /*is_even_key=*/true, "entitlement_id_5", kEntitlementKeyValue, + }; + EntitlementKeyInfo entitlement_6 = { + kTrackTypeSD, group_ids[1], + /*is_even_key=*/false, "entitlement_id_6", kEntitlementKeyValue, + }; + // Entitlement key sequence is not required. + ASSERT_OK(ecm_gen.Initialize( + params, {entitlement_6, injected_entitlement_two_, entitlement_3, + entitlement_4, entitlement_5, injected_entitlement_one_})); + auto ecm_serializer = absl::make_unique(); + const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get(); + ecm_gen.SetEcmSerializer(std::move(ecm_serializer)); + EntitledKeyInfo key1 = valid1_iv_16_8_; + EntitledKeyInfo key2 = valid2_iv_16_8_; + EcmSerializerParams expected_params; + expected_params.cas_id = params.cas_id; + expected_params.age_restriction = params.age_restriction; + expected_params.crypto_mode = params.crypto_mode; + expected_params.ecc_private_signing_key = params.ecc_private_signing_key; + expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id, + kWrappedKeyValue, key1.wrapped_key_iv, + key1.content_iv}; + expected_params.odd_key = {injected_entitlement_two_.key_id, key2.key_id, + kWrappedKeyValue, key2.wrapped_key_iv, + key2.content_iv}; + EcmSerializerGroupKeyInfo group_key_1; + group_key_1.group_id = entitlement_3.group_id; + group_key_1.even_key.entitlement_key_id = entitlement_3.key_id; + group_key_1.even_key.wrapped_key_value = kWrappedKeyValue; + group_key_1.odd_key.entitlement_key_id = entitlement_4.key_id; + group_key_1.odd_key.wrapped_key_value = kWrappedKeyValue; + EcmSerializerGroupKeyInfo group_key_2; + group_key_2.group_id = entitlement_5.group_id; + group_key_2.even_key.entitlement_key_id = entitlement_5.key_id; + group_key_2.even_key.wrapped_key_value = kWrappedKeyValue; + group_key_2.odd_key.entitlement_key_id = entitlement_6.key_id; + group_key_2.odd_key.wrapped_key_value = kWrappedKeyValue; + expected_params.group_keys = {group_key_1, group_key_2}; + EXPECT_CALL(*ecm_serializer_pointer, + SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull())) + .WillOnce(Return(OkStatus())); + std::string generated_ecm; + + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, group_ids, + &generated_ecm)); +} + +TEST_F(EcmTest, GenerateEcmWithMissingGroupKeysFail) { + FakeEcm ecm_gen; + 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_; + std::string generated_ecm; + + ASSERT_FALSE( + ecm_gen + .GenerateEcm(&key1, &key2, kTrackTypeSD, {"group_id"}, &generated_ecm) + .ok()); +} + class EcmTsPacketTest : public ::testing::Test {}; TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) { diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.cc b/media_cas_packager_sdk/internal/ecmg_client_handler.cc index f49a674..005e223 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.cc @@ -9,15 +9,19 @@ #include "media_cas_packager_sdk/internal/ecmg_client_handler.h" #include +#include #include #include "glog/logging.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/types/optional.h" #include "absl/types/span.h" #include "common/crypto_util.h" #include "common/random_util.h" +#include "common/status.h" #include "media_cas_packager_sdk/internal/ecmg_constants.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h" #include "media_cas_packager_sdk/internal/simulcrypt_constants.h" @@ -94,14 +98,54 @@ Status ProcessPrivateParameters(const char* const request, uint16_t param_type, absl::StrCat("Invalid parameter length ", param_length, " for parameter type ", param_type)); } - params->entitlement_comb.emplace_back(); - EntitlementIdKeyComb* combination = ¶ms->entitlement_comb.back(); - combination->key_id = - std::string(request + *offset, kEntitlementKeyIdSizeBytes); - *offset += kEntitlementKeyIdSizeBytes; - combination->key_value = - std::string(request + *offset, kEntitlementKeyValueSizeBytes); - *offset += kEntitlementKeyValueSizeBytes; + EntitlementKeyInfo entitlement_key; + entitlement_key.key_id.assign(request + *offset, + kEntitlementKeyIdSizeBytes); + entitlement_key.key_value.assign( + request + *offset + kEntitlementKeyIdSizeBytes, + kEntitlementKeyValueSizeBytes); + // If a key has been already received (without group id), then this is an + // odd key. Othervise, this is an even key. + entitlement_key.is_even_key = true; + for (const auto& existing_key : params->entitlement_keys) { + if (existing_key.group_id.empty()) { + entitlement_key.is_even_key = false; + break; + } + } + params->entitlement_keys.push_back(entitlement_key); + *offset += param_length; + break; + } + case ENTITLEMENT_ID_KEY_GROUP_COMBINATION: { + if (param_length <= + kEntitlementKeyIdSizeBytes + kEntitlementKeyValueSizeBytes) { + return Status(error::FAILED_PRECONDITION, + absl::StrCat("Invalid parameter length ", param_length, + " for parameter type ", param_type)); + } + EntitlementKeyInfo entitlement_key; + entitlement_key.key_id.assign(request + *offset, + kEntitlementKeyIdSizeBytes); + entitlement_key.key_value.assign( + request + *offset + kEntitlementKeyIdSizeBytes, + kEntitlementKeyValueSizeBytes); + entitlement_key.group_id.assign( + request + *offset + kEntitlementKeyIdSizeBytes + + kEntitlementKeyValueSizeBytes, + param_length - kEntitlementKeyIdSizeBytes - + kEntitlementKeyValueSizeBytes); + // If a key with the same group id has been already received, then this is + // an odd key. Othervise, this is an even key. + entitlement_key.is_even_key = true; + for (const auto& existing_key : params->entitlement_keys) { + if (existing_key.group_id == entitlement_key.group_id) { + entitlement_key.is_even_key = false; + break; + } + } + params->entitlement_keys.push_back(entitlement_key); + *offset += param_length; break; } case FINGERPRINTING_CONTROL: @@ -300,6 +344,10 @@ void BuildChannelError(uint16_t channel_id, uint16_t error_status, } uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "ChannelError sent. ECM_CHANNEL_ID: " << channel_id + << "; ERROR_STATUS: " << error_status + << "; ERROR_INFORMATION: " << error_info; } void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, @@ -340,11 +388,22 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, message_length); uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "ChannelStatus sent. ECM_CHANNEL_ID: " << channel_id + << "; SECTION_TSPKT_FLAG: " << kSectionTSpktFlag + << "; DELAY_START: " << config->delay_start + << "; DELAY_STOP: " << config->delay_stop + << "; ECM_REP_PERIOD: " << config->ecm_rep_period + << "; MAX_STREAMS: " << kMaxStream + << "; MIN_CP_DURATION: " << config->max_comp_time + << "; LEAD_CW: " << config->number_of_content_keys - 1 + << "; CW_PER_MESSAGE: " << config->number_of_content_keys + << "; MAX_COMP_TIME: " << config->max_comp_time; } -void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status, - const std::string& error_info, char* message, - size_t* message_length) { +void BuildStreamError(uint16_t channel_id, uint16_t stream_id, + uint16_t error_status, const std::string& error_info, + char* message, size_t* message_length) { DCHECK(message); DCHECK(message_length); simulcrypt_util::BuildMessageHeader( @@ -362,6 +421,11 @@ void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_st } uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "StreamError sent. ECM_CHANNEL_ID: " << channel_id + << "; ECM_STREAM_ID: " << stream_id + << "; ERROR_STATUS: " << error_status + << "; ERROR_INFORMATION: " << error_info; } uint16_t StatusToDvbErrorCode(const Status& status) { @@ -401,6 +465,11 @@ void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id, message_length); uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "StreamStatus sent. ECM_CHANNEL_ID: " << channel_id + << "; ECM_STREAM_ID: " << stream_id << "; ECM_ID: " << ecm_id + << "; ACCESS_CRITERIA_TRANSFER_MODE: " + << access_criteria_transfer_mode; } void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id, @@ -416,9 +485,13 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id, message_length); uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "StreamCloseResponse sent. ECM_CHANNEL_ID: " << channel_id + << "; ECM_STREAM_ID: " << stream_id; } -void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_number, +void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, + uint16_t cp_number, absl::Span ecm_datagram, char* message, size_t* message_length) { DCHECK(message); @@ -435,6 +508,9 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe ecm_datagram.size(), message, message_length); uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); + + VLOG(3) << "EcmResponse sent. ECM_CHANNEL_ID: " << channel_id + << "; ECM_STREAM_ID: " << stream_id << "; CP_NUMBER: " << cp_number; } Status CheckReceivedContentIv(const std::vector& content_ivs, @@ -460,32 +536,6 @@ Status CheckReceivedContentIv(const std::vector& content_ivs, return OkStatus(); } -std::vector ConvertEntitlementKeyInfo( - const std::vector& fetched_entitlements) { - std::vector converted; - // Only process if the fetched size is expected (1 or 2 entries). - if (fetched_entitlements.empty()) { - LOG(WARNING) << "EntitlementKeyFetcherFunc returned empty result."; - } else if (fetched_entitlements.size() > 2) { - LOG(ERROR) - << "EntitlementKeyFetcherFunc should return at most 2 entries, but " - << fetched_entitlements.size() << " entries are returned."; - } else { - converted.reserve(fetched_entitlements.size()); - for (auto const& fetched_entitlement : fetched_entitlements) { - EntitlementIdKeyComb entitlement_comb; - entitlement_comb.key_id = fetched_entitlement.key_id; - entitlement_comb.key_value = fetched_entitlement.key_value; - // In the case of two entitlement keys, the first in |entitlements| must - // be even key followed by the odd key. - converted.insert( - fetched_entitlement.is_even_key ? converted.begin() : converted.end(), - entitlement_comb); - } - } - return converted; -} - } // namespace EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) @@ -496,8 +546,9 @@ EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) ecmg_config_ = ecmg_config; } -void EcmgClientHandler::HandleRequest(const char* const request, char* response, - size_t* response_length) { +size_t EcmgClientHandler::HandleRequest(const char* const request, + char* response, + size_t* response_length) { DCHECK(request); DCHECK(response); @@ -507,16 +558,18 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, // 'offset' is used to track where we are within |request|. size_t offset = 0; memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE); - if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) { - BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response, - response_length); - return; - } offset += PROTOCOL_VERSION_SIZE; BigEndianToHost16(&request_type, request + offset); offset += MESSAGE_TYPE_SIZE; BigEndianToHost16(&request_length, request + offset); offset += MESSAGE_LENGTH_SIZE; + const size_t total_request_length = offset + request_length; + if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) { + BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response, + response_length); + return total_request_length; + } + EcmgParameters params; const bool should_sdk_process_access_criteria = custom_ac_processor_ == nullptr; @@ -526,7 +579,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, LOG(ERROR) << status.ToString(); BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status), status.error_message(), response, response_length); - return; + return total_request_length; } switch (request_type) { case ECMG_CHANNEL_SETUP: @@ -549,7 +602,14 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, break; case ECMG_CW_PROVISION: if (!should_sdk_process_access_criteria) { - UpdateParamsWithCustomAccessCriteriaProcessor(params); + status = UpdateParamsWithCustomAccessCriteriaProcessor(params); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + INVALID_MESSAGE, status.ToString(), response, + response_length); + return total_request_length; + } } HandleCwProvision(params, response, response_length); break; @@ -564,6 +624,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, response, response_length); break; } + return total_request_length; } void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, @@ -571,6 +632,9 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); + VLOG(3) << "ChannelSetup request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id; + // There is always one (and only one) channel per TCP connection. if (channel_id_set_) { BuildChannelError(params.ecm_channel_id, INVALID_MESSAGE, "", response, @@ -604,6 +668,7 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, } BuildChannelStatus(channel_id_, ecmg_config_, response, response_length); + VLOG(1) << "Channel " << channel_id_ << " opened"; } void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, @@ -611,6 +676,8 @@ void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, size_t* response_length) const { DCHECK(response); DCHECK(response_length); + VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id; + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, response_length); @@ -624,6 +691,8 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); + VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id; + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, response_length); @@ -632,11 +701,14 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, channel_id_set_ = false; streams_info_.clear(); *response_length = 0; + VLOG(1) << "Channel " << channel_id_ << " closed"; } void EcmgClientHandler::HandleChannelError(const EcmgParameters& params, char* response, size_t* response_length) const { + VLOG(3) << "ChannelError request. ECM_channel_id: " << params.ecm_channel_id; + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, response_length); @@ -659,6 +731,11 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); + VLOG(3) << "StreamSetup request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id + << "; ECM_id: " << params.ecm_id << "; nominal_CP_duration" + << params.nominal_cp_duration; + 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, @@ -689,21 +766,19 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params, } // Try to fetch entitlement keys using custom key fetcher if none is received. - std::vector& entitlements = - streams_info_[params.ecm_stream_id]->entitlement_comb; - if (entitlements.empty() && custom_key_fetcher_ != nullptr) { - const std::vector& fetched_entitlements = - custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id, - params.ecm_id); - const std::vector& converted_entitlements = - ConvertEntitlementKeyInfo(fetched_entitlements); - entitlements.assign(converted_entitlements.begin(), - converted_entitlements.end()); + if (custom_key_fetcher_ != nullptr) { + auto& entitlements = streams_info_[params.ecm_stream_id]->entitlement_keys; + if (entitlements.empty()) { + entitlements = custom_key_fetcher_(params.ecm_channel_id, + params.ecm_stream_id, params.ecm_id); + } } BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id, ecmg_config_->access_criteria_transfer_mode, response, response_length); + VLOG(1) << "Stream " << params.ecm_stream_id + << " opened. Number of streams open: " << streams_info_.size(); } void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, @@ -711,6 +786,9 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, size_t* response_length) const { DCHECK(response); DCHECK(response_length); + VLOG(3) << "StreamTest request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id; + 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, @@ -734,6 +812,9 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); + VLOG(3) << "StreamClose request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id; + 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, @@ -749,11 +830,16 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, streams_info_.erase(params.ecm_stream_id); BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id, response, response_length); + VLOG(1) << "Stream " << params.ecm_stream_id + << " closed. Number of streams open: " << streams_info_.size(); } void EcmgClientHandler::HandleStreamError(const EcmgParameters& params, char* response, size_t* response_length) const { + VLOG(3) << "StreamError request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id; + 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, @@ -778,11 +864,16 @@ void EcmgClientHandler::HandleStreamError(const EcmgParameters& params, *response_length = 0; } -void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( +Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( EcmgParameters& params) const { DCHECK(custom_ac_processor_ != nullptr); - const EcmgCustomParameters& custom_params = custom_ac_processor_( - params.ecm_channel_id, params.ecm_stream_id, params.access_criteria); + EcmgCustomParameters custom_params; + Status status = + custom_ac_processor_(params.ecm_channel_id, params.ecm_stream_id, + params.access_criteria, custom_params); + if (!status.ok()) { + return status; + } // Load results to |params|. if (custom_params.age_restriction >= 0) { params.age_restriction = custom_params.age_restriction; @@ -794,10 +885,7 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( params.content_ivs = custom_params.content_ivs; } if (!custom_params.entitlement_keys.empty()) { - const std::vector& converted_entitlements = - ConvertEntitlementKeyInfo(custom_params.entitlement_keys); - params.entitlement_comb.assign(converted_entitlements.begin(), - converted_entitlements.end()); + params.entitlement_keys = custom_params.entitlement_keys; } if (!custom_params.fingerprinting_control.empty()) { params.fingerprinting_control = custom_params.fingerprinting_control; @@ -805,11 +893,17 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( if (!custom_params.service_blocking_groups.empty()) { params.service_blocking_groups = custom_params.service_blocking_groups; } + return OkStatus(); } void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params, char* response, size_t* response_length) { + VLOG(3) << "CwProvision request. ECM_channel_id: " << params.ecm_channel_id + << "; ECM_stream_id: " << params.ecm_stream_id + << "; CP_number: " << params.cp_number + << "; CP_CW_Combination size: " << params.cp_cw_combinations.size(); + DCHECK(response); DCHECK(response_length); if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { @@ -882,12 +976,14 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters( return {error::INVALID_ARGUMENT, "Age restriction too large."}; } age_restriction_ = params.age_restriction; + VLOG(1) << "Channel age restriction has been set to: " << age_restriction_; } if (!params.crypto_mode.empty()) { if (!StringToCryptoMode(params.crypto_mode, &ecmg_config_->crypto_mode)) { return {error::INVALID_ARGUMENT, absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")}; } + VLOG(1) << "Channel crypto mode has been set to " << params.crypto_mode; } if (!params.content_ivs.empty()) { Status status = CheckReceivedContentIv( @@ -896,6 +992,8 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters( return status; } content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end()); + VLOG(1) << "Channel content IVs have been set with size " + << content_ivs_.size(); } return OkStatus(); } @@ -916,6 +1014,9 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters( if (stream_info->age_restriction != params.age_restriction) { stream_info->age_restriction = params.age_restriction; invalidate_ecm_gen = true; + VLOG(1) << "Stream " << params.ecm_stream_id + << " age restriction has been set to: " + << stream_info->age_restriction; } } @@ -928,6 +1029,8 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters( if (stream_info->crypto_mode != new_crypto_mode) { stream_info->crypto_mode = new_crypto_mode; invalidate_ecm_gen = true; + VLOG(1) << "Stream " << params.ecm_stream_id + << " crypto mode has been set to: " << params.crypto_mode; } } @@ -943,13 +1046,18 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters( } stream_info->content_ivs.assign(params.content_ivs.begin(), params.content_ivs.end()); + VLOG(1) << "Stream " << params.ecm_stream_id + << " content IVs have been set with size " + << stream_info->content_ivs.size(); } - if (!params.entitlement_comb.empty() && - stream_info->entitlement_comb != params.entitlement_comb) { - stream_info->entitlement_comb.assign(params.entitlement_comb.begin(), - params.entitlement_comb.end()); + if (!params.entitlement_keys.empty() && + stream_info->entitlement_keys != params.entitlement_keys) { + stream_info->entitlement_keys = params.entitlement_keys; invalidate_ecm_gen = true; + VLOG(1) << "Stream " << params.ecm_stream_id + << " entitlement keys have been set with size " + << stream_info->entitlement_keys.size(); } if (invalidate_ecm_gen) { @@ -967,14 +1075,22 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) { if (stream_info->content_ivs.empty()) { return {error::NOT_FOUND, "Content iv not specified."}; } - if (stream_info->entitlement_comb.empty()) { + + // Check number of keys for each group id (including empty group id). + absl::flat_hash_map group_id_to_key_count; + for (const auto& entitlement_key : stream_info->entitlement_keys) { + group_id_to_key_count[entitlement_key.group_id]++; + } + // Empty group id is the normal entitlement key just for this stream. + if (!group_id_to_key_count.contains("")) { return {error::NOT_FOUND, "Entitlement key id comb not specified."}; } - if (stream_info->entitlement_comb.size() != - ecmg_config_->number_of_content_keys) { - return {error::NOT_FOUND, - "Number of injected entitlement keys must equal to number of " - "content keys per ecm."}; + for (const auto& group_id_count : group_id_to_key_count) { + if (group_id_count.second != ecmg_config_->number_of_content_keys) { + return {error::NOT_FOUND, + "Number of injected entitlement keys must equal to number of " + "content keys per ecm."}; + } } bool key_rotation = ecmg_config_->number_of_content_keys > 1; @@ -989,19 +1105,19 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) { ecm_init_params.ecc_private_signing_key = ecmg_config_->ecc_private_signing_key; - std::vector entitlements; - entitlements.reserve(stream_info->entitlement_comb.size()); - for (size_t i = 0; i < stream_info->entitlement_comb.size(); i++) { - entitlements.emplace_back(); - EntitlementKeyInfo* entitlement = &entitlements.back(); - entitlement->track_type = kDefaultTrackType; - entitlement->is_even_key = key_rotation ? i % 2 == 0 : true; - entitlement->key_id = stream_info->entitlement_comb[i].key_id; - entitlement->key_value = stream_info->entitlement_comb[i].key_value; + // Override track type. This Simulcrypt ECMg implementation assumes only one + // track type -- all entitlement keys passed in will be used to generate ECM. + // It makes a copy to avoid changing the original one as the original one may + // be used to detect if there is change or not. + std::vector modified_entitlement_keys = + stream_info->entitlement_keys; + for (auto& entitlement_key : modified_entitlement_keys) { + entitlement_key.track_type = kDefaultTrackType; } stream_info->ecm = CreateEcmInstance(); - return stream_info->ecm->Initialize(ecm_init_params, entitlements); + return stream_info->ecm->Initialize(ecm_init_params, + modified_entitlement_keys); } Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, @@ -1085,14 +1201,23 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, service_blocking_params.device_groups.empty() ? nullptr : &service_blocking_params); + // Prepare group ids. All group ids of entitlement keys received will be used. + absl::flat_hash_set group_id_set; + for (const auto& entitlement_key : stream_info->entitlement_keys) { + if (!entitlement_key.group_id.empty()) { + group_id_set.insert(entitlement_key.group_id); + } + } + std::vector group_ids(group_id_set.begin(), group_id_set.end()); + Status status; std::string serialized_ecm; if (key_count > 1) { - status = stream_info->ecm->GenerateEcm(&keys[0], &keys[1], - kDefaultTrackType, &serialized_ecm); + status = stream_info->ecm->GenerateEcm( + &keys[0], &keys[1], kDefaultTrackType, group_ids, &serialized_ecm); } else { status = stream_info->ecm->GenerateSingleKeyEcm(&keys[0], kDefaultTrackType, - &serialized_ecm); + group_ids, &serialized_ecm); } if (!status.ok()) { return status; diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.h b/media_cas_packager_sdk/internal/ecmg_client_handler.h index cd16f57..982b747 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.h +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.h @@ -10,6 +10,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_ #include +#include #include #include #include @@ -27,23 +28,10 @@ namespace cas { // A struct that represent a CP_CW_Combination. struct EcmgCpCwCombination { - uint16_t cp; // crypto period + uint16_t cp; // crypto period std::string cw; // control word }; -// A struct that represent a Entitlement_Id_Key_Combination. -struct EntitlementIdKeyComb { - std::string key_id; // must be 16 bytes - std::string key_value; // must be 32 bytes - - bool operator==(const EntitlementIdKeyComb& other) const { - return (other.key_id == key_id) && (other.key_value == key_value); - } - bool operator!=(const EntitlementIdKeyComb& other) const { - return !operator==(other); - } -}; - // A struct that is used to hold all possible params for a ECMG request. struct EcmgParameters { std::vector cp_cw_combinations; // Size is 1 or 2. @@ -61,7 +49,7 @@ struct EcmgParameters { int age_restriction = -1; // Negative value to indidate the field is not set. std::string crypto_mode; std::vector content_ivs; // 8 or 16 bytes, one for each key. - std::vector entitlement_comb; + std::vector entitlement_keys; std::string access_criteria; std::string fingerprinting_control; std::vector service_blocking_groups; @@ -73,7 +61,7 @@ struct EcmgStreamInfo { CryptoMode crypto_mode; // 8 or 16 bytes, one for each key. std::vector content_ivs; - std::vector entitlement_comb; + std::vector entitlement_keys; std::unique_ptr ecm; uint8_t age_restriction; }; @@ -90,8 +78,9 @@ class EcmgClientHandler { // Handle a |request| from the client. // If any response is generated, it would returned via |response| // and |response_length|. - virtual void HandleRequest(const char* const request, char* response, - size_t* response_length); + // Returns length of |request| that has been processed. + virtual size_t HandleRequest(const char* const request, char* response, + size_t* response_length); // Sets the custom entitlement key fetching function used by ECMG to fetch // entitlement keys. If entitlement key information is not received in @@ -145,7 +134,7 @@ class EcmgClientHandler { size_t* response_length) const; void HandleCwProvision(const EcmgParameters& params, char* response, size_t* response_length); - void UpdateParamsWithCustomAccessCriteriaProcessor( + Status UpdateParamsWithCustomAccessCriteriaProcessor( EcmgParameters& params) const; // Update channel level private parameters using |params|. 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 0650901..faf2890 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include "testing/gmock.h" @@ -20,6 +21,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/types/span.h" +#include "common/status.h" #include "example/test_ecmg_messages.h" #include "media_cas_packager_sdk/internal/ecm.h" #include "media_cas_packager_sdk/internal/ecmg_constants.h" @@ -36,8 +38,12 @@ using simulcrypt_util::AddUint16Param; using simulcrypt_util::AddUint32Param; using simulcrypt_util::AddUint8Param; using simulcrypt_util::BuildMessageHeader; +using ::testing::_; using ::testing::ByMove; +using ::testing::ElementsAre; using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::NotNull; using ::testing::Return; constexpr size_t kBufferSize = 1024; @@ -69,28 +75,29 @@ constexpr int kExpectedEcmResponseLength = sizeof(kTestEcmgEcmResponse); constexpr char kFingerprintingControl[] = "control"; constexpr char kServiceBlockingGroup1[] = "group1"; constexpr char kServiceBlockingGroup2[] = "group2"; +constexpr char kEntitlementGroupId[] = "GroupId"; std::vector FakeEntitlementKeyFetcherFunc( uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) { - return {{kTrackTypesSD, /*is_even_key*/ false, kEntitlementKeyIdOdd, - kEntitlementKeyValueOdd}, - {kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdEven, - kEntitlementKeyValueEven}}; + return {{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; } -EcmgCustomParameters FakeCustomAcProcessorFunc( - uint16_t /*channel_id*/, uint16_t /*stream_id*/, - const std::string& access_criteria) { - EcmgCustomParameters params; +Status FakeCustomAcProcessorFunc(uint16_t /*channel_id*/, + uint16_t /*stream_id*/, + const std::string& access_criteria, + EcmgCustomParameters& params) { params.age_restriction = access_criteria.empty() ? 0 : 3; params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd}; params.crypto_mode = kCryptoMode; - params.entitlement_keys = {{kTrackTypesSD, /*is_even_key*/ false, - kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, - {kTrackTypesSD, /*is_even_key*/ true, - kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; - - return params; + params.entitlement_keys = { + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; + return OkStatus(); } class MockEcmgClientHandler : public EcmgClientHandler { @@ -178,18 +185,21 @@ class EcmgClientHandlerTest : public ::testing::Test { AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length); } if (!crypto_mode.empty()) { - AddParam(CRYPTO_MODE, reinterpret_cast(crypto_mode.c_str()), + AddParam(CRYPTO_MODE, + reinterpret_cast(crypto_mode.c_str()), crypto_mode.size(), message, message_length); } uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); } - void BuildStreamSetupRequest(uint16_t channel_id, uint16_t stream_id, - uint16_t ecm_id, uint16_t nominal_CP_duration, - const std::vector& entitlements, - const std::vector& content_ivs, - char* message, size_t* message_length) { + void BuildStreamSetupRequest( + uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id, + uint16_t nominal_CP_duration, + const std::vector& entitlements, + const std::vector& group_entitlements, + const std::vector& content_ivs, char* message, + size_t* message_length) { EXPECT_TRUE(message != nullptr); EXPECT_TRUE(message_length != nullptr); BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message, @@ -207,9 +217,17 @@ class EcmgClientHandlerTest : public ::testing::Test { entitlement.size(), message, message_length); } } + if (!group_entitlements.empty()) { + for (const auto& entitlement : group_entitlements) { + AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION, + reinterpret_cast(entitlement.c_str()), + entitlement.size(), message, message_length); + } + } if (!content_ivs.empty()) { for (auto& content_iv : content_ivs) { - AddParam(CONTENT_IV, reinterpret_cast(content_iv.c_str()), + AddParam(CONTENT_IV, + reinterpret_cast(content_iv.c_str()), content_iv.size(), message, message_length); } } @@ -253,8 +271,8 @@ class EcmgClientHandlerTest : public ::testing::Test { Host16ToBigEndian(message + 3, &total_param_length); } - void CheckChannelError(uint16_t expected_error_code, const char* const response, - size_t response_length) { + void CheckChannelError(uint16_t expected_error_code, + const char* const response, size_t response_length) { EXPECT_LE(sizeof(kTestChannelErrorResponse), response_length); // Message version and message type EXPECT_EQ(0, memcmp(kTestChannelErrorResponse, response, 3)); @@ -265,8 +283,8 @@ class EcmgClientHandlerTest : public ::testing::Test { EXPECT_EQ(expected_error_code & 0xff, response[16]); } - void CheckStreamError(uint16_t expected_error_code, const char* const response, - size_t response_length) { + void CheckStreamError(uint16_t expected_error_code, + const char* const response, size_t response_length) { EXPECT_LE(sizeof(kTestStreamErrorResponse), response_length); // Message version and message type EXPECT_EQ(0, memcmp(kTestStreamErrorResponse, response, 3)); @@ -374,7 +392,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) { kChannelId, kStreamId, kEcmId, kNominalCpDuration, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {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_)); @@ -383,7 +402,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) { kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_, + &request_len_); handler_->HandleRequest(request_, response_, &response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); @@ -414,7 +434,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) { // No entitlement keys are injected. BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, - /*entitlements=*/{}, + /*entitlements=*/{}, /*group_entitlements=*/{}, {kContentKeyIvEven, kContentKeyIvOdd}, request_, &request_len_); handler_->HandleRequest(request_, response_, &response_len_); @@ -482,7 +502,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) { kChannelId, kStreamId, kEcmId, kNominalCpDuration, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {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_)); @@ -614,7 +635,8 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) { 0, kStreamId, kEcmId, kNominalCpDuration, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_, + &request_len_); handler_->HandleRequest(request_, response_, &response_len_); CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); } @@ -642,7 +664,7 @@ TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) { EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, - /*entitlements*/ {}, + /*entitlements*/ {}, /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_, &request_len_); handler_->HandleRequest(request_, response_, &response_len_); @@ -668,7 +690,8 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) { BuildStreamSetupRequest( kChannelId, kStreamId, kEcmId, kNominalCpDuration, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {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_)); @@ -694,7 +717,8 @@ TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) { {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd), absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)}, - {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + /*group_entitlements=*/{}, {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_)); @@ -766,7 +790,7 @@ TEST_F(EcmgClientHandlerTest, NewCasIdEmbeddedInEcmSuccess) { ASSERT_EQ(response_len_, sizeof(kTestEcmgEcmResponse)); uint16_t actual_cas_id = (static_cast(response_[35]) << 8) | - static_cast(response_[36]); + static_cast(response_[36]); EXPECT_EQ(actual_cas_id, expected_cas_id); } @@ -806,10 +830,20 @@ class MockEcm : public Ecm { public: MockEcm() = default; ~MockEcm() override = default; + MOCK_METHOD(Status, Initialize, + (const EcmInitParameters&, + const std::vector&), + (override)); MOCK_METHOD(void, SetFingerprinting, (const EcmFingerprintingParams*), (override)); MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*), (override)); + MOCK_METHOD(Status, GenerateEcm, + (EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key, + const std::string& track_type, + const std::vector& group_ids, + std::string* serialized_ecm), + (const, override)); }; EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id, @@ -895,6 +929,149 @@ TEST_F(EcmgClientHandlerTest, SetServiceBlockingViaAceessCriteriaSuccess) { handler_->HandleRequest(request_, response_, &response_len_); } +TEST_F(EcmgClientHandlerTest, ProcessedRequestLengthExpected) { + EXPECT_EQ( + handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_), + sizeof(kTestEcmgChannelSetup)); +} + +TEST_F(EcmgClientHandlerTest, ExtraRequestProcessedRequestLengthExpected) { + char request[2048]; + memcpy(request, kTestEcmgChannelSetup, sizeof(kTestEcmgChannelSetup)); + memcpy(request + sizeof(kTestEcmgChannelSetup), kTestEcmgChannelSetup, + sizeof(kTestEcmgChannelSetup)); + + EXPECT_EQ(handler_->HandleRequest(request, response_, &response_len_), + sizeof(kTestEcmgChannelSetup)); +} + +TEST_F(EcmgClientHandlerTest, NoGroupEntitlementKeysSuccess) { + auto ecm = absl::make_unique(); + MockEcm* ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_.get(), CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EXPECT_CALL(*ecm_ptr, Initialize).Times(1); + EXPECT_CALL(*ecm_ptr, GenerateEcm(NotNull(), NotNull(), _, + /*group_ids=*/IsEmpty(), NotNull())) + .Times(1); + + // Set up channel. + BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction, + kCryptoMode, request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + ASSERT_EQ(response_len_, sizeof(kTestEcmgChannelStatus)); + // Set up stream with group enetitlements. + BuildStreamSetupRequest( + kChannelId, kStreamId, kEcmId, kNominalCpDuration, + {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), + absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, + /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_, + &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + ASSERT_EQ(response_len_, sizeof(kTestEcmgStreamStatus)); + // Do provisioing. + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); +} + +MATCHER(IsExpectedEntitlements, "") { + const std::vector expected = { + {"track_type", /*group_id=*/"", /*is_even_key=*/true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}, + {"track_type", /*group_id=*/"", /*is_even_key=*/false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {"track_type", kEntitlementGroupId, /*is_even_key=*/true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}, + {"track_type", kEntitlementGroupId, /*is_even_key=*/false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}}; + return arg == expected; +} + +TEST_F(EcmgClientHandlerTest, GroupEntitlementKeysViaStreamSetupSuccess) { + auto ecm = absl::make_unique(); + MockEcm* ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_.get(), CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EXPECT_CALL(*ecm_ptr, Initialize(_, IsExpectedEntitlements())).Times(1); + EXPECT_CALL(*ecm_ptr, + GenerateEcm(NotNull(), NotNull(), _, + ElementsAre(kEntitlementGroupId), NotNull())) + .Times(1); + + // Set up channel. + BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction, + kCryptoMode, request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + ASSERT_EQ(response_len_, sizeof(kTestEcmgChannelStatus)); + // Set up stream with group enetitlements. + BuildStreamSetupRequest( + kChannelId, kStreamId, kEcmId, kNominalCpDuration, + {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), + absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, + {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven, + kEntitlementGroupId), + absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd, + kEntitlementGroupId)}, + {kContentKeyEven, kContentKeyEven}, request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + ASSERT_EQ(response_len_, sizeof(kTestEcmgStreamStatus)); + // Do provisioing. + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); +} + +Status FakeCustomAcProcessorFuncWithGroupKeys( + uint16_t /*channel_id*/, uint16_t /*stream_id*/, + const std::string& access_criteria, EcmgCustomParameters& params) { + params.age_restriction = 0; + params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd}; + params.crypto_mode = kCryptoMode; + params.entitlement_keys = { + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}, + {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, + {kTrackTypesSD, kEntitlementGroupId, /*is_even_key=*/true, + kEntitlementKeyIdEven, kEntitlementKeyValueEven}, + {kTrackTypesSD, kEntitlementGroupId, /*is_even_key=*/false, + kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}}; + return OkStatus(); +} + +TEST_F(EcmgClientHandlerTest, GroupEntitlementKeysViaCallbackSuccess) { + auto ecm = absl::make_unique(); + MockEcm* ecm_ptr = ecm.get(); + EXPECT_CALL(*handler_.get(), CreateEcmInstance) + .Times(1) + .WillOnce(Return(ByMove(std::move(ecm)))); + EXPECT_CALL(*ecm_ptr, Initialize(_, IsExpectedEntitlements())).Times(1); + EXPECT_CALL(*ecm_ptr, + GenerateEcm(NotNull(), NotNull(), _, + ElementsAre(kEntitlementGroupId), NotNull())) + .Times(1); + + handler_->SetCustomAccessCriteriaProcessFunc( + FakeCustomAcProcessorFuncWithGroupKeys); + SetupValidStandardChannelStream(); + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); +} + +Status FakeCustomAcProcessorFuncReturningError( + uint16_t /*channel_id*/, uint16_t /*stream_id*/, + const std::string& /*access_criteria*/, EcmgCustomParameters& /*params*/) { + return Status(error::FAILED_PRECONDITION, "Invalid"); +} + +TEST_F(EcmgClientHandlerTest, CustomAccessCriteriaProcessorReturnsError) { + handler_->SetCustomAccessCriteriaProcessFunc( + FakeCustomAcProcessorFuncReturningError); + SetupValidStandardChannelStream(); + + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + + CheckStreamError(INVALID_MESSAGE, response_, response_len_); +} + } // namespace } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/ecmg_constants.h b/media_cas_packager_sdk/internal/ecmg_constants.h index 1dd9396..173779e 100644 --- a/media_cas_packager_sdk/internal/ecmg_constants.h +++ b/media_cas_packager_sdk/internal/ecmg_constants.h @@ -68,6 +68,7 @@ #define ENTITLEMENT_ID_KEY_COMBINATION (0x8004) #define FINGERPRINTING_CONTROL (0x8005) #define SERVICE_BLOCKING_GROUP (0x8006) +#define ENTITLEMENT_ID_KEY_GROUP_COMBINATION (0x8007) // ECMG protocol error values. #define INVALID_MESSAGE (0x0001) diff --git a/media_cas_packager_sdk/internal/emm.cc b/media_cas_packager_sdk/internal/emm.cc index 1bf66b5..524fb3f 100644 --- a/media_cas_packager_sdk/internal/emm.cc +++ b/media_cas_packager_sdk/internal/emm.cc @@ -9,6 +9,7 @@ #include "media_cas_packager_sdk/internal/emm.h" #include +#include #include "glog/logging.h" #include "absl/strings/str_cat.h" diff --git a/media_cas_packager_sdk/internal/emm.h b/media_cas_packager_sdk/internal/emm.h index 51e89c0..33cc685 100644 --- a/media_cas_packager_sdk/internal/emm.h +++ b/media_cas_packager_sdk/internal/emm.h @@ -9,6 +9,7 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_ +#include #include #include diff --git a/media_cas_packager_sdk/internal/emm_test.cc b/media_cas_packager_sdk/internal/emm_test.cc index 9bd7291..17aa46c 100644 --- a/media_cas_packager_sdk/internal/emm_test.cc +++ b/media_cas_packager_sdk/internal/emm_test.cc @@ -8,6 +8,7 @@ #include "media_cas_packager_sdk/internal/emm.h" +#include #include #include "testing/gmock.h" @@ -215,7 +216,8 @@ class EmmTest : public ::testing::Test { EXPECT_EQ(signature, expected_signature); } int GetPayloadLength(const std::string& serialized_emm) { - return static_cast(serialized_emm[kPayloadLengthStartIndex]) << 8 | + return static_cast(serialized_emm[kPayloadLengthStartIndex]) + << 8 | static_cast(serialized_emm[kPayloadLengthStartIndex + 1]); } diff --git a/media_cas_packager_sdk/internal/emmg.cc b/media_cas_packager_sdk/internal/emmg.cc index b3a9463..40e2d4e 100644 --- a/media_cas_packager_sdk/internal/emmg.cc +++ b/media_cas_packager_sdk/internal/emmg.cc @@ -10,6 +10,7 @@ #include +#include #include #include diff --git a/media_cas_packager_sdk/internal/emmg.h b/media_cas_packager_sdk/internal/emmg.h index f5ca551..b99b0a9 100644 --- a/media_cas_packager_sdk/internal/emmg.h +++ b/media_cas_packager_sdk/internal/emmg.h @@ -10,6 +10,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_ #include +#include #include #include diff --git a/media_cas_packager_sdk/internal/emmg_test.cc b/media_cas_packager_sdk/internal/emmg_test.cc index 68b18ce..5a2ed27 100644 --- a/media_cas_packager_sdk/internal/emmg_test.cc +++ b/media_cas_packager_sdk/internal/emmg_test.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include "testing/gunit.h" diff --git a/media_cas_packager_sdk/internal/mpeg2ts.h b/media_cas_packager_sdk/internal/mpeg2ts.h index ce453e8..8def341 100644 --- a/media_cas_packager_sdk/internal/mpeg2ts.h +++ b/media_cas_packager_sdk/internal/mpeg2ts.h @@ -11,6 +11,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_ #include +#include #include diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.cc b/media_cas_packager_sdk/internal/simulcrypt_util.cc index 6fdc823..aa0de2f 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.cc +++ b/media_cas_packager_sdk/internal/simulcrypt_util.cc @@ -8,6 +8,7 @@ #include "media_cas_packager_sdk/internal/simulcrypt_util.h" +#include #include #include "glog/logging.h" @@ -74,8 +75,8 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, *message_length += param_length; } -void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, - char* message, size_t* message_length) { +void AddParam(uint16_t param_type, const uint8_t* param_value, + uint16_t param_length, char* message, size_t* message_length) { DCHECK(param_value); DCHECK(message); DCHECK(message_length); diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.h b/media_cas_packager_sdk/internal/simulcrypt_util.h index 4dcf3f8..b15dcdd 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.h +++ b/media_cas_packager_sdk/internal/simulcrypt_util.h @@ -12,6 +12,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_ #include +#include #include @@ -44,8 +45,8 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, size_t* message_length); // Add a param that is |param_length| bytes long. -void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, - char* message, size_t* message_length); +void AddParam(uint16_t param_type, const uint8_t* param_value, + uint16_t param_length, char* message, size_t* message_length); } // namespace simulcrypt_util } // namespace cas diff --git a/media_cas_packager_sdk/internal/ts_packet.cc b/media_cas_packager_sdk/internal/ts_packet.cc index a0122ab..d39b2f8 100644 --- a/media_cas_packager_sdk/internal/ts_packet.cc +++ b/media_cas_packager_sdk/internal/ts_packet.cc @@ -9,6 +9,7 @@ #include "media_cas_packager_sdk/internal/ts_packet.h" #include +#include #include "glog/logging.h" #include "absl/strings/str_cat.h" diff --git a/media_cas_packager_sdk/internal/ts_packet.h b/media_cas_packager_sdk/internal/ts_packet.h index b3f3919..dbe2068 100644 --- a/media_cas_packager_sdk/internal/ts_packet.h +++ b/media_cas_packager_sdk/internal/ts_packet.h @@ -33,6 +33,7 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_ +#include #include #include @@ -78,7 +79,9 @@ class TsPacket { uint32_t transport_scrambling_control() const { return transport_scrambling_control_; } - uint32_t adaptation_field_control() const { return adaptation_field_control_; } + uint32_t adaptation_field_control() const { + return adaptation_field_control_; + } uint32_t continuity_counter() const { return continuity_counter_; } const std::string& payload() const { return payload_; } @@ -94,7 +97,9 @@ class TsPacket { void set_transport_scrambling_control(uint32_t v) { transport_scrambling_control_ = v; } - void set_adaptation_field_control(uint32_t v) { adaptation_field_control_ = v; } + void set_adaptation_field_control(uint32_t v) { + adaptation_field_control_ = v; + } void set_continuity_counter(uint32_t v) { continuity_counter_ = v; } void set_payload(const std::string& p) { payload_ = p; } diff --git a/media_cas_packager_sdk/internal/util.cc b/media_cas_packager_sdk/internal/util.cc index 46278eb..12da7a9 100644 --- a/media_cas_packager_sdk/internal/util.cc +++ b/media_cas_packager_sdk/internal/util.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include "glog/logging.h" diff --git a/media_cas_packager_sdk/internal/util.h b/media_cas_packager_sdk/internal/util.h index 3aaeb29..68bebf2 100644 --- a/media_cas_packager_sdk/internal/util.h +++ b/media_cas_packager_sdk/internal/util.h @@ -11,6 +11,7 @@ #include +#include #include #include diff --git a/media_cas_packager_sdk/internal/util_test.cc b/media_cas_packager_sdk/internal/util_test.cc index ee381ba..74b655d 100644 --- a/media_cas_packager_sdk/internal/util_test.cc +++ b/media_cas_packager_sdk/internal/util_test.cc @@ -10,6 +10,8 @@ #include +#include + #include "testing/gmock.h" #include "testing/gunit.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h" diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index 8058769..aa8eb98 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -34,6 +34,8 @@ cc_binary( deps = [ ":wv_cas_ca_descriptor", ":wv_cas_ecm", + ":wv_cas_ecmg_client_handler", + ":wv_cas_emm", ":wv_cas_key_fetcher", ":wv_cas_types", ], 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 d6ab327..c0cf19b 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc @@ -9,6 +9,7 @@ #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" #include +#include #include #include "glog/logging.h" 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 428457c..540a763 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h @@ -11,6 +11,7 @@ #include +#include #include #include @@ -60,7 +61,8 @@ class WvCasCaDescriptor { // ECM stream). The descriptor will be 6 bytes plus any bytes added as // (user-defined) private data. virtual Status GenerateCaDescriptor( - uint16_t ca_pid, const std::string& provider, const std::string& content_id, + uint16_t ca_pid, const std::string& provider, + const std::string& content_id, const std::vector& entitlement_key_ids, std::string* serialized_ca_desc) const; 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 852d5b2..744930a 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,8 @@ #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" +#include + #include "testing/gmock.h" #include "testing/gunit.h" #include "protos/public/media_cas.pb.h" 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 index 432a9cb..5b9ed83 100644 --- a/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc +++ b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc @@ -8,6 +8,8 @@ #include "media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h" +#include + #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "curl/curl.h" diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index 19c4c12..f06217d 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -8,6 +8,7 @@ #include "media_cas_packager_sdk/public/wv_cas_ecm.h" +#include #include #include "glog/logging.h" @@ -69,6 +70,7 @@ void WvCasEcm::SetServiceBlocking( Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key, const WvCasContentKeyInfo& odd_key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const { EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key); EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key); @@ -84,11 +86,12 @@ Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key, } } return ecm_->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type, - serialized_ecm); + group_ids, serialized_ecm); } Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const { EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key); // Make content key to 16 bytes if crypto mode is Csa2. @@ -97,7 +100,8 @@ Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, entitled_key.key_value = entitled_key.key_value + entitled_key.key_value; } } - return ecm_->GenerateSingleKeyEcm(&entitled_key, track_type, serialized_ecm); + return ecm_->GenerateSingleKeyEcm(&entitled_key, track_type, group_ids, + serialized_ecm); } Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid, diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index e191f08..35a5342 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -9,6 +9,7 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ +#include #include #include #include @@ -98,6 +99,8 @@ class WvCasEcm { // |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. + // |group_ids| If specified, the content keys will be additionally encrypted + // by group entitlement keys with specified |group_ids|. // |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. @@ -106,6 +109,7 @@ class WvCasEcm { virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key, const WvCasContentKeyInfo& odd_key, const std::string& track_type, + const std::vector& group_ids, std::string* serialized_ecm) const; // Constructs a Widevine ECM using the provided key info. This call is @@ -113,11 +117,14 @@ class WvCasEcm { // Args: // |key| information for key to be encoded into ECM. // |track_type| the track that the key is being used to encrypt. + // |group_ids| If specified, the content keys will be additionally encrypted + // by group entitlement keys with specified |group_ids|. // |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, + const std::vector& group_ids, std::string* serialized_ecm) const; // Generate a TS packet with the given |ecm| as payload. 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 400e6b9..13c8ffb 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -143,7 +143,7 @@ TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) { EXPECT_EQ(error::INVALID_ARGUMENT, wv_cas_ecm .GenerateSingleKeyEcm(content_keys[0], kDefaultTrackTypeSd, - &actual_ecm) + /*group_ids=*/{}, &actual_ecm) .error_code()); } @@ -160,7 +160,7 @@ TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) { EXPECT_EQ(error::INVALID_ARGUMENT, wv_cas_ecm .GenerateEcm(content_keys[0], content_keys[1], - kDefaultTrackTypeSd, &actual_ecm) + kDefaultTrackTypeSd, /*group_ids=*/{}, &actual_ecm) .error_code()); } @@ -177,10 +177,11 @@ TEST_P(WvCasEcmTest, GenerateEcmSuccess) { std::string actual_ecm; if (key_rotation_) { EXPECT_OK(wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], - kDefaultTrackTypeSd, &actual_ecm)); + kDefaultTrackTypeSd, /*group_ids=*/{}, + &actual_ecm)); } else { EXPECT_OK(wv_cas_ecm.GenerateSingleKeyEcm( - content_keys[0], kDefaultTrackTypeSd, &actual_ecm)); + content_keys[0], kDefaultTrackTypeSd, /*group_ids=*/{}, &actual_ecm)); } EXPECT_EQ(absl::BytesToHexString(actual_ecm).substr(0, 7), "4ad4020"); if (content_iv_size_ == 8) { diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc index f500e53..ed745d3 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.cc @@ -11,6 +11,7 @@ #include "glog/logging.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "common/status.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h" namespace widevine { @@ -55,12 +56,11 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size, const char* const request_buffer, size_t response_buffer_size, char* response_buffer, - size_t* response_length) { - if (request_buffer == nullptr || response_buffer == nullptr || - response_length == nullptr) { + size_t& response_length, + size_t& processed_request_length) { + if (request_buffer == nullptr || response_buffer == nullptr) { std::string error_message = - "request_buffer, response_buffer and response_length must " - "not be null pointer."; + "request_buffer and response_buffer must not be null pointer."; LOG(ERROR) << error_message; return {error::INVALID_ARGUMENT, error_message}; } @@ -69,7 +69,7 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size, absl::StrCat("request_buffer_size is too small. Minimum required is ", kSimulcryptMessageHeaderSizeBytes, " bytes."); LOG(ERROR) << error_message; - *response_length = 0; + response_length = 0; return {error::INVALID_ARGUMENT, error_message}; } if (response_buffer_size < kMinimumResponseBufferSizeBytes) { @@ -77,11 +77,15 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size, absl::StrCat("response_buffer_size is too small. Minimum required is ", kMinimumResponseBufferSizeBytes, " bytes."); LOG(ERROR) << error_message; - *response_length = 0; + response_length = 0; return {error::INVALID_ARGUMENT, error_message}; } - inner_handler_->HandleRequest(request_buffer, response_buffer, - response_length); + processed_request_length = inner_handler_->HandleRequest( + request_buffer, response_buffer, &response_length); + if (processed_request_length == 0) { + return {error::INTERNAL, "request not processed."}; + } + return OkStatus(); } diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h index 083f356..df27548 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler.h @@ -66,10 +66,13 @@ class WvCasEcmgClientHandler { // enough to hold the max possible response message (at least 2048 bytes). // - |response_buffer| is the buffer that holds the response message. // - |response_length| is the actual length of the response message. + // - |processed_request_length| is the actual length of |request_buffer| that + // has been processed. Status HandleRequest(size_t request_buffer_size, const char* const request_buffer, size_t response_buffer_size, char* response_buffer, - size_t* response_length); + size_t& response_length, + size_t& processed_request_length); protected: // For unit test only. diff --git a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc index 882b97c..0f03218 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecmg_client_handler_test.cc @@ -24,7 +24,7 @@ constexpr size_t kBufferSize = 2048; class MockEcmgClientHandler : public EcmgClientHandler { public: MockEcmgClientHandler() : EcmgClientHandler(&config_) {} - MOCK_METHOD(void, HandleRequest, + MOCK_METHOD(size_t, HandleRequest, (const char* const request, char* response, size_t* response_length), (override)); @@ -47,15 +47,17 @@ TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) { size_t response_buffer_size = kBufferSize; char response_buffer[kBufferSize]; size_t response_length = 0; + size_t processed_length = 0; auto mock_internal_handler = absl::make_unique(); EXPECT_CALL(*mock_internal_handler, - HandleRequest(request_buffer, response_buffer, &response_length)); + HandleRequest(request_buffer, response_buffer, &response_length)) + .WillOnce(testing::DoAll(testing::Return(100))); auto handler = absl::make_unique( std::move(mock_internal_handler)); EXPECT_OK(handler->HandleRequest(request_buffer_size, request_buffer, response_buffer_size, response_buffer, - &response_length)); + response_length, processed_length)); } class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test { @@ -65,6 +67,7 @@ class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test { size_t response_buffer_size_ = kBufferSize; char response_buffer_[kBufferSize]; size_t response_length_ = 0; + size_t processed_length_ = 0; }; TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) { @@ -76,7 +79,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) { EXPECT_EQ(handler ->HandleRequest(/*request_buffer_size=*/1, request_buffer_, response_buffer_size_, response_buffer_, - &response_length_) + response_length_, processed_length_) .error_code(), error::INVALID_ARGUMENT); EXPECT_EQ(response_length_, 0); @@ -91,7 +94,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferTooSmallFail) { EXPECT_EQ(handler ->HandleRequest(request_buffer_size_, request_buffer_, /*response_buffer_size=*/1, response_buffer_, - &response_length_) + response_length_, processed_length_) .error_code(), error::INVALID_ARGUMENT); EXPECT_EQ(response_length_, 0); @@ -107,7 +110,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferNullFail) { handler ->HandleRequest(request_buffer_size_, /*request_buffer=*/nullptr, response_buffer_size_, - response_buffer_, &response_length_) + response_buffer_, response_length_, processed_length_) .error_code(), error::INVALID_ARGUMENT); EXPECT_EQ(response_length_, 0); @@ -122,24 +125,27 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferNullFail) { EXPECT_EQ(handler ->HandleRequest(request_buffer_size_, request_buffer_, response_buffer_size_, - /*response_buffer=*/nullptr, &response_length_) + /*response_buffer=*/nullptr, response_length_, + processed_length_) .error_code(), error::INVALID_ARGUMENT); EXPECT_EQ(response_length_, 0); } -TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseLengthNullFail) { +TEST_F(WvCasEcmgClientHandlerInvalidInputTest, HandleRequestReturnedZeroFail) { auto mock_internal_handler = absl::make_unique(); - EXPECT_CALL(*mock_internal_handler, HandleRequest).Times(0); + // Processed length returned is zero. + EXPECT_CALL(*mock_internal_handler, HandleRequest) + .WillOnce(testing::DoAll(testing::Return(0))); + auto handler = absl::make_unique( std::move(mock_internal_handler)); - EXPECT_EQ(handler ->HandleRequest(request_buffer_size_, request_buffer_, response_buffer_size_, response_buffer_, - /*response_length=*/nullptr) + response_length_, processed_length_) .error_code(), - error::INVALID_ARGUMENT); + error::INTERNAL); } } // namespace diff --git a/media_cas_packager_sdk/public/wv_cas_emm.cc b/media_cas_packager_sdk/public/wv_cas_emm.cc index 0d858f3..28d663e 100644 --- a/media_cas_packager_sdk/public/wv_cas_emm.cc +++ b/media_cas_packager_sdk/public/wv_cas_emm.cc @@ -8,6 +8,8 @@ #include "media_cas_packager_sdk/public/wv_cas_emm.h" +#include + #include "glog/logging.h" #include "absl/memory/memory.h" #include "absl/types/span.h" diff --git a/media_cas_packager_sdk/public/wv_cas_emm.h b/media_cas_packager_sdk/public/wv_cas_emm.h index 60f9a80..79a9a61 100644 --- a/media_cas_packager_sdk/public/wv_cas_emm.h +++ b/media_cas_packager_sdk/public/wv_cas_emm.h @@ -9,6 +9,7 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_ +#include #include #include #include diff --git a/media_cas_packager_sdk/public/wv_cas_emm_test.cc b/media_cas_packager_sdk/public/wv_cas_emm_test.cc index ee7da7b..1c1da7d 100644 --- a/media_cas_packager_sdk/public/wv_cas_emm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_emm_test.cc @@ -8,6 +8,7 @@ #include "media_cas_packager_sdk/public/wv_cas_emm.h" +#include #include #include diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index c907623..41d439a 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -9,8 +9,10 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_ +#include #include #include +#include #include #include "common/status.h" @@ -52,18 +54,34 @@ 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 { + // The track that the key is being used to encrypt. It used as the identifier + // to select which enetitlement keys to use when generating ECM. Must be + // non-empty. std::string track_type; + // Used only if this is a group entitlement key (leave empty in case of non + // group key). If specified, it used as the secondary (to |track_type|) + // identifier to select which group enetitlement keys to use when generating + // ECM. + std::string group_id; + // If this entitlement key is even key or not. bool is_even_key; - std::string key_id; // must be 16 bytes. - std::string key_value; // must be 32 bytes. + // Key ID of the entitlement key, must be 16 bytes. + std::string key_id; + // Key value of the entitlement key, must be 32 bytes. Used to encrypt content + // keys in the generated ECMs. + std::string key_value; + + bool operator==(const EntitlementKeyInfo& other) const { + return std::forward_as_tuple(track_type, group_id, is_even_key, key_id, + key_value) == + std::forward_as_tuple(other.track_type, other.group_id, + other.is_even_key, other.key_id, + other.key_value); + } + bool operator!=(const EntitlementKeyInfo& other) const { + return !(*this == other); + } }; enum class EcmVersion : int { kV2 = 0, kV3 = 1 }; @@ -172,9 +190,13 @@ struct EcmgCustomParameters { // |stream_id| is the stream id received at ECMG from SCS when setting up // the stream. // |access_criteria| the received access criteria from SCS. -// Returns EcmgCustomParameters. Negative or empty fields will be ignored. -typedef std::function +// |ecmg_params| is the output parameters by processing |access_criteria|. +// Negative or empty fields will be ignored. +// Returns a Status. If status is not OK, error INVALID_MESSAGE with error info +// from status will be sent to SCS. +typedef std::function CustomAccessCriteriaProcessFunc; struct EcmFingerprintingParams { diff --git a/media_cas_packager_sdk/public/wv_ecmg.cc b/media_cas_packager_sdk/public/wv_ecmg.cc index 20fa199..bd937cd 100644 --- a/media_cas_packager_sdk/public/wv_ecmg.cc +++ b/media_cas_packager_sdk/public/wv_ecmg.cc @@ -9,12 +9,14 @@ // Widevine MediaCAS ECMG server. #include +#include #include #include #include #include #include +#include #include #include #include @@ -58,7 +60,7 @@ ABSL_FLAG( "cryptography key."); #define LISTEN_QUEUE_SIZE (20) -#define BUFFER_SIZE (1024) +#define BUFFER_SIZE (2048) using widevine::cas::EcmgClientHandler; using widevine::cas::EcmgConfig; @@ -104,7 +106,6 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) { char response[BUFFER_SIZE]; while (true) { bzero(request, BUFFER_SIZE); - bzero(response, BUFFER_SIZE); size_t response_length = 0; size_t request_length = recv(socket_fd, request, BUFFER_SIZE, 0); if (request_length == 0) { @@ -116,11 +117,21 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) { return; } PrintMessage("Request", request, request_length); - ecmg->HandleRequest(request, response, &response_length); - PrintMessage("Response", response, response_length); - if (send(socket_fd, response, response_length, 0) < 0) { - LOG(INFO) << "Failed to send response to client"; - return; + size_t processed_total_length = 0; + while (processed_total_length < request_length) { + bzero(response, BUFFER_SIZE); + size_t processed_length = ecmg->HandleRequest( + &request[0] + processed_total_length, response, &response_length); + if (processed_length == 0) { + LOG(ERROR) << "Failed to process the request"; + return; + } + processed_total_length += processed_length; + PrintMessage("Response", response, response_length); + if (send(socket_fd, response, response_length, 0) < 0) { + LOG(ERROR) << "Failed to send response to client"; + return; + } } } } @@ -171,6 +182,9 @@ int main(int argc, char** argv) { break; } + // Ignoring SIGCHLD signal to prevent Zombie processes. + signal(SIGCHLD, SIG_IGN); + // While loop to serve different client connections. while (true) { struct sockaddr_in client_address; @@ -179,7 +193,7 @@ int main(int argc, char** argv) { listen_socket_fd, reinterpret_cast(&client_address), &client_address_size); LOG(INFO) << "\nTCP connection " << client_socket_fd << " start\n"; - if (client_socket_fd < 0) { + if (client_socket_fd < 0 && errno != EINTR) { LOG(ERROR) << "Failed to accept connection request from client"; } else { // TODO(user): Consider limit the number of forked child processes. @@ -200,8 +214,5 @@ int main(int argc, char** argv) { usleep(1000); } - // Close listening socket. - close(listen_socket_fd); - return 0; } diff --git a/media_cas_packager_sdk/public/wv_emmg.cc b/media_cas_packager_sdk/public/wv_emmg.cc index bcce7dd..f795bec 100644 --- a/media_cas_packager_sdk/public/wv_emmg.cc +++ b/media_cas_packager_sdk/public/wv_emmg.cc @@ -13,6 +13,7 @@ #include #include +#include #include #include diff --git a/protos/public/hash_algorithm.proto b/protos/public/hash_algorithm.proto index bcb0751..91677b9 100644 --- a/protos/public/hash_algorithm.proto +++ b/protos/public/hash_algorithm.proto @@ -17,4 +17,5 @@ enum HashAlgorithmProto { HASH_ALGORITHM_UNSPECIFIED = 0; HASH_ALGORITHM_SHA_1 = 1; HASH_ALGORITHM_SHA_256 = 2; + HASH_ALGORITHM_SHA_384 = 3; } diff --git a/protos/public/media_cas.proto b/protos/public/media_cas.proto index 405ecac..33dfbb3 100644 --- a/protos/public/media_cas.proto +++ b/protos/public/media_cas.proto @@ -22,6 +22,9 @@ message CaDescriptorPrivateData { // Entitlement key IDs for current content per track. Each track will allow up // to 2 entitlement key ids (odd and even entitlement keys). repeated bytes entitlement_key_ids = 3; + + // The groups ids this channel belongs to. + repeated bytes group_ids = 4; } // Widevine fingerprinting. @@ -49,6 +52,15 @@ message ServiceBlocking { message EmmPayload { repeated Fingerprinting fingerprinting = 1; repeated ServiceBlocking service_blocking = 2; + // Epoch time in seconds. The time when the EMM is generated. + optional int64 timestamp_secs = 3; +} + +message SignedEmmPayload { + // Serialized EmmPayload. + optional bytes serialized_payload = 1; + // ECC (Elliptic Curve Cryptography) signature of |serialized_payload|. + optional bytes signature = 2; } message EcmMetaData { @@ -89,6 +101,18 @@ message EcmKeyData { optional bytes content_iv = 4; } +message EcmGroupKeyData { + // Group id of this key data. + optional bytes group_id = 1; + // Required. The key data for the even slot. Fields wrapped_key_iv and + // content_iv may be omitted if it is the same as EcmPayload.even_key_data. + optional EcmKeyData even_key_data = 2; + // Optional. The key data for the odd slot if key rotation is enabled. Fields + // wrapped_key_iv and content_iv may be omitted if it is the same as + // EcmPayload.odd_key_data. + optional EcmKeyData odd_key_data = 3; +} + message EcmPayload { // Required. Meta info carried by the ECM. optional EcmMetaData meta_data = 1; @@ -100,6 +124,9 @@ message EcmPayload { optional Fingerprinting fingerprinting = 4; // Optional. Widevine service blocking information. optional ServiceBlocking service_blocking = 5; + // If a channel belongs to a group, the content keys can additionally be + // encrypted by the group entitlement keys. + repeated EcmGroupKeyData group_key_data = 6; } // The payload field for an ECM with signature. diff --git a/protos/public/media_cas_encryption.proto b/protos/public/media_cas_encryption.proto index c40712a..12c3b27 100644 --- a/protos/public/media_cas_encryption.proto +++ b/protos/public/media_cas_encryption.proto @@ -27,9 +27,14 @@ message CasEncryptionRequest { // returned. optional bool key_rotation = 4; // Optional value which can be used to indicate a group. - // If present the CasEncryptionResponse will return key based on the group - // id. + // If present, the CasEncryptionResponse will return keys based on this group + // id, instead of |content_id|. optional bytes group_id = 5; + // Entitlement period index for media using entitlement key rotation. It + // always corresponds to the entitlement key period. If present, the + // entitlement keys returned will corresponds to the specified entitlement + // period index. + optional uint32 entitlement_period_index = 6; } message CasEncryptionResponse { diff --git a/strings/serialize_test.cc b/strings/serialize_test.cc index 38e5347..6050133 100644 --- a/strings/serialize_test.cc +++ b/strings/serialize_test.cc @@ -10,6 +10,8 @@ #include "strings/serialize.h" +#include + #include "testing/gunit.h" namespace widevine { diff --git a/util/endian/endian_test.cc b/util/endian/endian_test.cc index 60ca0f8..4961318 100644 --- a/util/endian/endian_test.cc +++ b/util/endian/endian_test.cc @@ -7,6 +7,8 @@ //////////////////////////////////////////////////////////////////////////////// #include "util/endian/endian.h" +#include + #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/strings/escaping.h"