Support for group license

Content keys in ECM v3 can now additionally be encrypted by group
entitlement keys.
This commit is contained in:
Lu Chen
2021-03-04 14:35:08 -08:00
parent 79e39b482d
commit 62777d7d3b
66 changed files with 1275 additions and 954 deletions

View File

@@ -21,14 +21,21 @@ git_repository(
git_repository( git_repository(
name = "com_google_protobuf", name = "com_google_protobuf",
remote = "https://github.com/google/protobuf.git", 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. # Bazel custom build rule support.
git_repository( git_repository(
name = "bazel_skylib", name = "bazel_skylib",
remote = "https://github.com/bazelbuild/bazel-skylib.git", 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. # Protobuf library support. Not included in the recent protobuf release.

View File

@@ -34,6 +34,7 @@ cc_library(
hdrs = ["playready_interface.h"], hdrs = ["playready_interface.h"],
deps = [ deps = [
"//util:error_space", "//util:error_space",
"//protos/public:external_license_cc_proto",
"//protos/public:license_protocol_cc_proto", "//protos/public:license_protocol_cc_proto",
], ],
) )
@@ -43,6 +44,7 @@ cc_library(
hdrs = ["playready_sdk_impl.h"], hdrs = ["playready_sdk_impl.h"],
deps = [ deps = [
":playready_interface", ":playready_interface",
"//protos/public:external_license_cc_proto",
"//protos/public:license_protocol_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( cc_library(
name = "private_key_util", name = "private_key_util",
hdrs = ["private_key_util.h"], hdrs = ["private_key_util.h"],
@@ -1218,3 +1242,45 @@ cc_test(
"//protos/public:remote_attestation_cc_proto", "//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",
],
)

View File

@@ -8,6 +8,7 @@
#include "common/aes_cbc_util.h" #include "common/aes_cbc_util.h"
#include <cstdint>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>

View File

@@ -10,6 +10,8 @@
#include "common/crypto_util.h" #include "common/crypto_util.h"
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"
#include "absl/strings/string_view.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 >> 16) & 0xFF);
message.append(1, (size_bits >> 8) & 0xFF); message.append(1, (size_bits >> 8) & 0xFF);
message.append(1, size_bits & 0xFF); message.append(1, size_bits & 0xFF);
if (CMAC_Update(cmac_ctx, reinterpret_cast<const uint8_t*>(message.data()), if (CMAC_Update(cmac_ctx,
reinterpret_cast<const uint8_t*>(message.data()),
message.size())) { message.size())) {
size_t reslen; size_t reslen;
unsigned char res[AES_BLOCK_SIZE]; unsigned char res[AES_BLOCK_SIZE];

View File

@@ -12,6 +12,7 @@
#ifndef COMMON_CRYPTO_UTIL_H_ #ifndef COMMON_CRYPTO_UTIL_H_
#define COMMON_CRYPTO_UTIL_H_ #define COMMON_CRYPTO_UTIL_H_
#include <cstdint>
#include <string> #include <string>
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"

View File

@@ -62,6 +62,8 @@ std::string GetMessageDigest(const std::string& message,
case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kUnspecified:
case widevine::HashAlgorithm::kSha256: case widevine::HashAlgorithm::kSha256:
return widevine::Sha256_Hash(message); return widevine::Sha256_Hash(message);
case widevine::HashAlgorithm::kSha384:
return widevine::Sha384_Hash(message);
case widevine::HashAlgorithm::kSha1: case widevine::HashAlgorithm::kSha1:
LOG(ERROR) << "Unexpected hash algorithm: " LOG(ERROR) << "Unexpected hash algorithm: "
<< static_cast<int>(hash_algorithm); << static_cast<int>(hash_algorithm);

View File

@@ -11,7 +11,7 @@
namespace widevine { namespace widevine {
enum class HashAlgorithm { kUnspecified, kSha1, kSha256 }; enum class HashAlgorithm { kUnspecified, kSha1, kSha256, kSha384 };
} // namespace widevine } // namespace widevine

View File

@@ -25,7 +25,10 @@
#include "common/rsa_key.h" #include "common/rsa_key.h"
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "openssl/asn1.h"
#include "openssl/bn.h" #include "openssl/bn.h"
#include "openssl/digest.h" #include "openssl/digest.h"
#include "openssl/err.h" #include "openssl/err.h"
@@ -60,6 +63,8 @@ std::string GetMessageDigest(const std::string& message,
case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kUnspecified:
case widevine::HashAlgorithm::kSha1: case widevine::HashAlgorithm::kSha1:
return widevine::Sha1_Hash(message); return widevine::Sha1_Hash(message);
case widevine::HashAlgorithm::kSha384:
return widevine::Sha384_Hash(message);
case widevine::HashAlgorithm::kSha256: case widevine::HashAlgorithm::kSha256:
return widevine::Sha256_Hash(message); return widevine::Sha256_Hash(message);
} }
@@ -73,6 +78,8 @@ const EVP_MD* GetHashMd(widevine::HashAlgorithm hash_algorithm) {
case widevine::HashAlgorithm::kUnspecified: case widevine::HashAlgorithm::kUnspecified:
case widevine::HashAlgorithm::kSha1: case widevine::HashAlgorithm::kSha1:
return EVP_sha1(); return EVP_sha1();
case widevine::HashAlgorithm::kSha384:
return EVP_sha384();
case widevine::HashAlgorithm::kSha256: case widevine::HashAlgorithm::kSha256:
return EVP_sha256(); return EVP_sha256();
} }

View File

@@ -14,6 +14,7 @@
#ifndef COMMON_RSA_KEY_H_ #ifndef COMMON_RSA_KEY_H_
#define COMMON_RSA_KEY_H_ #define COMMON_RSA_KEY_H_
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>

View File

@@ -14,6 +14,8 @@
#ifndef COMMON_SECURITY_PROFILE_LIST_H_ #ifndef COMMON_SECURITY_PROFILE_LIST_H_
#define COMMON_SECURITY_PROFILE_LIST_H_ #define COMMON_SECURITY_PROFILE_LIST_H_
#include <cstdint>
#include "absl/synchronization/mutex.h" #include "absl/synchronization/mutex.h"
#include "common/hash_algorithm.h" #include "common/hash_algorithm.h"
#include "common/status.h" #include "common/status.h"

View File

@@ -8,6 +8,8 @@
#include "common/sha_util.h" #include "common/sha_util.h"
#include <cstdint>
#include <cstdint> #include <cstdint>
#include "openssl/sha.h" #include "openssl/sha.h"
@@ -29,6 +31,14 @@ std::string Sha256_Hash(const std::string& message) {
return digest; return digest;
} }
std::string Sha384_Hash(const std::string& message) {
std::string digest;
digest.resize(SHA384_DIGEST_LENGTH);
SHA384(reinterpret_cast<const uint8_t*>(message.data()), message.size(),
reinterpret_cast<uint8_t*>(&digest[0]));
return digest;
}
std::string Sha512_Hash(const std::string& message) { std::string Sha512_Hash(const std::string& message) {
std::string digest; std::string digest;
digest.resize(SHA512_DIGEST_LENGTH); digest.resize(SHA512_DIGEST_LENGTH);

View File

@@ -21,6 +21,9 @@ std::string Sha1_Hash(const std::string& message);
// Calculates SHA256 hash. // Calculates SHA256 hash.
std::string Sha256_Hash(const std::string& message); std::string Sha256_Hash(const std::string& message);
// Calculates SHA384 hash.
std::string Sha384_Hash(const std::string& message);
// Calculate SHA512 hash. // Calculate SHA512 hash.
std::string Sha512_Hash(const std::string& message); std::string Sha512_Hash(const std::string& message);

View File

@@ -12,6 +12,7 @@
#include <stdio.h> #include <stdio.h>
#include <cassert> #include <cassert>
#include <cstdint>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
@@ -166,11 +167,12 @@ int main(int argc, char** argv) {
std::string ecm; std::string ecm;
widevine::Status status; widevine::Status status;
if (kKeyRotation) { if (kKeyRotation) {
status = wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], status =
kDefaultTrackTypeSd, &ecm); wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, /*group_ids=*/{}, &ecm);
} else { } else {
status = wv_cas_ecm.GenerateSingleKeyEcm(content_keys[0], status = wv_cas_ecm.GenerateSingleKeyEcm(
kDefaultTrackTypeSd, &ecm); content_keys[0], kDefaultTrackTypeSd, /*group_ids=*/{}, &ecm);
} }
if (!status.ok()) { if (!status.ok()) {

View File

@@ -8,6 +8,7 @@
// Example of how to use the wv_cas_emm library. // Example of how to use the wv_cas_emm library.
#include <cstdint>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <string> #include <string>

View File

@@ -9,12 +9,14 @@
// Example of Simulcrypt ECMG with custom entitlement key fetcher. // Example of Simulcrypt ECMG with custom entitlement key fetcher.
#include <netinet/in.h> #include <netinet/in.h>
#include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include <cerrno> #include <cerrno>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
@@ -76,7 +78,6 @@ void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) {
char response[kBufferSizeBytes]; char response[kBufferSizeBytes];
while (true) { while (true) {
bzero(request, kBufferSizeBytes); bzero(request, kBufferSizeBytes);
bzero(response, kBufferSizeBytes);
size_t response_length = 0; size_t response_length = 0;
size_t request_length = recv(socket_fd, request, kBufferSizeBytes, 0); size_t request_length = recv(socket_fd, request, kBufferSizeBytes, 0);
if (request_length == 0) { if (request_length == 0) {
@@ -88,12 +89,23 @@ void ServeClient(int socket_fd, WvCasEcmgClientHandler* ecmg) {
return; return;
} }
PrintMessage("Request", request, request_length); PrintMessage("Request", request, request_length);
ecmg->HandleRequest(kBufferSizeBytes, request, kBufferSizeBytes, response, size_t processed_total_length = 0;
&response_length); while (processed_total_length < request_length) {
PrintMessage("Response", response, response_length); bzero(response, kBufferSizeBytes);
if (send(socket_fd, response, response_length, 0) < 0) { size_t processed_length = 0;
std::cerr << "Failed to send response to client." << std::endl; ecmg->HandleRequest(kBufferSizeBytes - processed_total_length,
return; 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<EntitlementKeyInfo> MyEntitlementKeyFetcherFun(uint16_t channel_id,
constexpr char entitlement_key_id_odd[] = "fake_key_id2...."; constexpr char entitlement_key_id_odd[] = "fake_key_id2....";
constexpr char entitlement_key_value_odd[] = constexpr char entitlement_key_value_odd[] =
"fakefakefakefakefakefakefake2..."; "fakefakefakefakefakefakefake2...";
return {{track_types_hd, /*is_even_key=*/true, entitlement_key_id_even, return {{track_types_hd, /*group_id=*/"", /*is_even_key=*/true,
entitlement_key_value_even}, entitlement_key_id_even, entitlement_key_value_even},
{track_types_hd, /*is_even_key=*/false, entitlement_key_id_odd, {track_types_hd, /*group_id=*/"", /*is_even_key=*/false,
entitlement_key_value_odd}}; entitlement_key_id_odd, entitlement_key_value_odd}};
} }
int create_listen_socket_fd() { int create_listen_socket_fd() {
@@ -181,6 +193,9 @@ int main(int argc, char** argv) {
break; break;
} }
// Ignoring SIGCHLD signal to prevent Zombie processes.
signal(SIGCHLD, SIG_IGN);
// While loop to serve different client connections. // While loop to serve different client connections.
while (true) { while (true) {
struct sockaddr_in client_address; struct sockaddr_in client_address;
@@ -190,7 +205,7 @@ int main(int argc, char** argv) {
&client_address_size); &client_address_size);
std::cout << "\nTCP connection " << client_socket_fd << " start" std::cout << "\nTCP connection " << client_socket_fd << " start"
<< std::endl; << std::endl;
if (client_socket_fd < 0) { if (client_socket_fd < 0 && errno != EINTR) {
std::cerr << "Failed to accept connection request from client" std::cerr << "Failed to accept connection request from client"
<< std::endl; << std::endl;
} else { } else {

View File

@@ -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( cc_library(
name = "ecmg_client_handler", name = "ecmg_client_handler",
srcs = ["ecmg_client_handler.cc"], srcs = ["ecmg_client_handler.cc"],
@@ -98,6 +70,7 @@ cc_library(
":util", ":util",
"//base", "//base",
"@abseil_repo//absl/container:flat_hash_map", "@abseil_repo//absl/container:flat_hash_map",
"@abseil_repo//absl/container:flat_hash_set",
"@abseil_repo//absl/memory", "@abseil_repo//absl/memory",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"@abseil_repo//absl/types:optional", "@abseil_repo//absl/types:optional",
@@ -123,6 +96,7 @@ cc_test(
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",
"@abseil_repo//absl/strings:str_format", "@abseil_repo//absl/strings:str_format",
"@abseil_repo//absl/types:span", "@abseil_repo//absl/types:span",
"//common:status",
"//example:test_ecmg_messages", "//example:test_ecmg_messages",
], ],
) )
@@ -309,6 +283,7 @@ cc_library(
deps = [ deps = [
":ecm_serializer", ":ecm_serializer",
"//base", "//base",
"@abseil_repo//absl/container:flat_hash_set",
"@abseil_repo//absl/status", "@abseil_repo//absl/status",
"@abseil_repo//absl/status:statusor", "@abseil_repo//absl/status:statusor",
"@abseil_repo//absl/strings", "@abseil_repo//absl/strings",

View File

@@ -11,6 +11,9 @@
#include <stddef.h> #include <stddef.h>
#include <bitset> #include <bitset>
#include <cstddef>
#include <cstdint>
#include <utility>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
@@ -76,10 +79,14 @@ Status Ecm::Initialize(
return {error::INVALID_ARGUMENT, "Invalid CA system ID."}; return {error::INVALID_ARGUMENT, "Invalid CA system ID."};
} }
ClearEntitlementKeys(); entitlement_keys_.clear();
for (const auto& entitlement : injected_entitlements) { for (const auto& entitlement : injected_entitlements) {
PushEntitlementKey(entitlement.track_type, entitlement.is_even_key, std::vector<EntitlementKeyIdValue>& map_entry =
{entitlement.key_id, entitlement.key_value}); 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()) { if (!CheckEntitlementKeys()) {
@@ -114,13 +121,14 @@ void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) {
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { std::string* serialized_ecm) const {
absl::ReaderMutexLock lock(&ecm_params_mutex_); absl::ReaderMutexLock lock(&ecm_params_mutex_);
if (!initialized_) { if (!initialized_) {
return {error::INTERNAL, "Not initialized."}; return {error::INTERNAL, "Not initialized."};
} }
if (!HaveEntitlementKeys()) { if (entitlement_keys_.empty()) {
return {error::INTERNAL, "Need entitlement key."}; return {error::INTERNAL, "Need entitlement key."};
} }
if (even_key == nullptr) { if (even_key == nullptr) {
@@ -134,31 +142,45 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
return {error::INVALID_ARGUMENT, return {error::INVALID_ARGUMENT,
"odd_key can not be null as key rotation is enabled."}; "odd_key can not be null as key rotation is enabled."};
} }
if (serialized_ecm == nullptr) { if (serialized_ecm == nullptr) {
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."}; return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
} }
const bool has_odd_key = odd_key != nullptr;
std::vector<EntitledKeyInfo*> keys = {even_key};
if (odd_key != nullptr) {
keys.push_back(odd_key);
}
Status status = WrapEntitledKeys(track_type, keys);
if (!status.ok()) {
return status;
}
EcmSerializerParams serializer_params; EcmSerializerParams serializer_params;
serializer_params.crypto_mode = crypto_mode_; serializer_params.crypto_mode = crypto_mode_;
serializer_params.age_restriction = age_restriction_; serializer_params.age_restriction = age_restriction_;
serializer_params.cas_id = cas_id_; serializer_params.cas_id = cas_id_;
serializer_params.even_key = {even_key->entitlement_key_id, even_key->key_id,
even_key->wrapped_key_value, // Process normal content keys.
even_key->wrapped_key_iv, even_key->content_iv}; std::vector<EntitledKeyInfo> content_keys = {*even_key};
if (odd_key != nullptr) { std::vector<EcmSerializerKeyInfo*> serializer_keys = {
serializer_params.odd_key = {odd_key->entitlement_key_id, odd_key->key_id, &serializer_params.even_key};
odd_key->wrapped_key_value, if (has_odd_key) {
odd_key->wrapped_key_iv, odd_key->content_iv}; 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<EcmSerializerKeyInfo*> 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_; serializer_params.fingerprinting = fingerprinting_;
@@ -175,12 +197,13 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { 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, Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t* continuity_counter, uint8_t table_id, uint8_t* continuity_counter,
absl::Span<uint8_t> packet, absl::Span<uint8_t> packet,
ssize_t* bytes_modified) { ssize_t* bytes_modified) {
if (ecm.empty()) { if (ecm.empty()) {
@@ -207,38 +230,49 @@ Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table
return OkStatus(); return OkStatus();
} }
Status Ecm::WrapEntitledKeys(const std::string& track_type, Status Ecm::WrapEntitledKeys(
const std::vector<EntitledKeyInfo*>& keys) const { const std::string& track_type, const std::string& group_id,
if (!initialized_) { const std::vector<EntitledKeyInfo>& content_keys,
return {error::INTERNAL, "Not initialized."}; std::vector<EcmSerializerKeyInfo*> serializer_keys) const {
} DCHECK(initialized_);
if (keys.empty()) { if (content_keys.empty()) {
return {error::INVALID_ARGUMENT, "Vector of EntitledKeyInfo is 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()) { if (ekey_map_entry == entitlement_keys_.end()) {
return {error::INTERNAL, "No Entitlement Key found for given track_type."}; return {error::INTERNAL, "No Entitlement Key found for given track_type."};
} }
const std::vector<EntitlementKeyIdValue>& entitlement_keys =
ekey_map_entry->second;
const auto& ekey_list = ekey_map_entry->second; if (entitlement_keys.size() != content_keys.size()) {
if (ekey_list.size() != keys.size()) {
return {error::INTERNAL, 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 (int i = 0; i < entitlement_keys.size(); ++i) {
for (auto entitled_key : keys) { // Fill these fields only if this is NOT a group key.
entitled_key->entitlement_key_id = entitlement_key->key_id; if (group_id.empty()) {
// Wrap key using entitlement key. serializer_keys[i]->wrapped_key_iv = content_keys[i].wrapped_key_iv;
Status status = serializer_keys[i]->content_iv = content_keys[i].content_iv;
WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv, serializer_keys[i]->content_key_id = content_keys[i].key_id;
entitled_key->key_value, &entitled_key->wrapped_key_value); }
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()) { if (!status.ok()) {
return status; return status;
} }
entitlement_key++;
} }
return OkStatus(); return OkStatus();
} }
@@ -291,20 +325,12 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const {
return OkStatus(); 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 { bool Ecm::CheckEntitlementKeys() const {
for (const auto& track : entitlement_keys_) { for (const auto& track : entitlement_keys_) {
if (track.second.size() < (paired_keys_required_ ? 2 : 1)) { if (track.second.size() != (paired_keys_required_ ? 2 : 1)) {
LOG(ERROR) << " Wrong number of entitlement keys for track " LOG(ERROR) << " Wrong number of entitlement keys: "
<< track.first << ": " << (paired_keys_required_ ? 2 : 1) << (paired_keys_required_ ? 2 : 1) << " expected, "
<< " expected, " << track.second.size() << " received."; << track.second.size() << " received.";
return false; return false;
} }
} }

View File

@@ -10,6 +10,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <list> #include <list>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -111,6 +112,8 @@ class Ecm {
// |even_key| information for even key to be encoded into ECM. // |even_key| information for even key to be encoded into ECM.
// |odd_key| information for odd 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. // |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. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |even_key| and |odd_key| contents (specifically IV sizes) must be // The |even_key| and |odd_key| contents (specifically IV sizes) must be
// consistent with the initialized settings. // consistent with the initialized settings.
@@ -119,6 +122,7 @@ class Ecm {
virtual Status GenerateEcm(EntitledKeyInfo* even_key, virtual Status GenerateEcm(EntitledKeyInfo* even_key,
EntitledKeyInfo* odd_key, EntitledKeyInfo* odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const; std::string* serialized_ecm) const;
// Accept a key and IV and construct an ECM that will fit into a Transport // Accept a key and IV and construct an ECM that will fit into a Transport
@@ -127,11 +131,14 @@ class Ecm {
// Args: // Args:
// |key| information for key to be encoded into ECM. // |key| information for key to be encoded into ECM.
// |track_type| the track that the key is being used to encrypt. // |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. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent // The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings. // with the initialized settings.
virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key, virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const; std::string* serialized_ecm) const;
// Generate a TS packet with the given |ecm| as payload. // Generate a TS packet with the given |ecm| as payload.
@@ -165,16 +172,6 @@ class Ecm {
ecm_serializer_ = std::move(ecm_serializer); 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<EntitledKeyInfo*>& keys) const;
private: private:
// Entitlement key - |key_value| is used to wrap the content key, and |key_id| // 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 // is added to the ECM. |track_type| allows the entitlement key to be
@@ -184,32 +181,23 @@ class Ecm {
std::string key_value; 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. // Verify that each track has the right number of keys. Complain if not.
bool CheckEntitlementKeys() const; bool CheckEntitlementKeys() const;
// Remove all current entitlement keys. // Apply the entitlement keys with the given track type (and group_ids if
void ClearEntitlementKeys() { entitlement_keys_.clear(); } // specified) to wrap the given |keys|.
// Args:
// Add an entitlement key to our current state. Even key is placed first. // |track_type| the track type for the entitlement keys that should be used.
void PushEntitlementKey(const std::string& track_type, bool is_even_key, // |group_id| group id of the entitlement keys to use. Can be empty.
const EntitlementKeyIdValue& key) { // |keys| the content keys to be wrapped.
auto emplaced = entitlement_keys_.emplace( // |serializer_keys| is the output vector. It must have the same size as
track_type, std::list<EntitlementKeyIdValue>{}); // |keys|. Fields in EcmSerializerKeyInfo will be updated. If group_id is
if (is_even_key) { // non-empty, only fields entitlement_key_id and wrapped_key_value will be
emplaced.first->second.push_front(key); // updated.
} else { Status WrapEntitledKeys(
emplaced.first->second.push_back(key); const std::string& track_type, const std::string& group_id,
} const std::vector<EntitledKeyInfo>& content_keys,
} std::vector<EcmSerializerKeyInfo*> serializer_keys) const;
// Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|. // Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|.
// Returns the resulting wrapped key in |wrapped_key|. // Returns the resulting wrapped key in |wrapped_key|.
@@ -236,8 +224,11 @@ class Ecm {
std::string ecc_private_signing_key_; std::string ecc_private_signing_key_;
// Entitlement keys needed for ECM generation. // Entitlement keys needed for ECM generation.
// The keys are added when the CasEncryptionResponse is processed. // The keys are added when the CasEncryptionResponse is processed.
// Maps from track_type to one/two EntitlementKeyIdValue with even key first. // Maps from {track_type, group_id} to one/two EntitlementKeyIdValue with even
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_; // key first.
std::map<std::pair<std::string, std::string>,
std::vector<EntitlementKeyIdValue>>
entitlement_keys_;
std::unique_ptr<EcmSerializer> ecm_serializer_; std::unique_ptr<EcmSerializer> ecm_serializer_;
mutable absl::Mutex ecm_params_mutex_; mutable absl::Mutex ecm_params_mutex_;

View File

@@ -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<EntitledKeyInfo> 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<EntitledKeyInfo>* 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

View File

@@ -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 <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <cstdint>
#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<KeyParameters> 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) { ecm_ = std::move(ecm); }
private:
friend class EcmGeneratorTest;
Status ProcessEcmParameters(const EcmParameters& ecm_params,
std::vector<EntitledKeyInfo>* 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> ecm_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_

View File

@@ -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<EntitledKeyInfo>* 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<EntitlementKeyInfo> entitlements;
ASSERT_OK(key_fetcher.ParseEntitlementResponse(entitlement_response,
&entitlements));
ecm_ = absl::make_unique<Ecm>();
ASSERT_OK(ecm_->Initialize(ecm_init_params_, entitlements));
ecm_gen_.set_ecm(std::move(ecm_));
}
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> 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<EntitledKeyInfo> 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<EntitledKeyInfo> 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

View File

@@ -11,6 +11,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -32,6 +33,13 @@ struct EcmSerializerKeyInfo {
std::string content_iv; 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. // Parameters passed into ECM serializer to generate ECM.
struct EcmSerializerParams { struct EcmSerializerParams {
uint16_t cas_id; uint16_t cas_id;
@@ -44,6 +52,7 @@ struct EcmSerializerParams {
// Private signing key used to sign ECM data. Must be an elliptic-curve // Private signing key used to sign ECM data. Must be an elliptic-curve
// cryptography key. // cryptography key.
std::string ecc_private_signing_key; std::string ecc_private_signing_key;
std::vector<EcmSerializerGroupKeyInfo> group_keys;
}; };
enum class EcmSerializerVersion { kV2 = 0, kV3 }; enum class EcmSerializerVersion { kV2 = 0, kV3 };

View File

@@ -7,6 +7,7 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h" #include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -8,8 +8,11 @@
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h" #include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
#include <bitset> #include <bitset>
#include <cstddef>
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h" #include "absl/status/status.h"
#include "absl/status/statusor.h" #include "absl/status/statusor.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
@@ -67,6 +70,57 @@ Status ValidateKeyInfo(const EcmSerializerKeyInfo& key_info, bool is_even_key) {
return OkStatus(); 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<EcmSerializerGroupKeyInfo>& group_keys,
bool has_odd_key) {
// Used to make sure group ids are unique.
absl::flat_hash_set<std::string> 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( EcmMetaData::CipherMode ConvertCryptoModeToProtoCipherMode(
CryptoMode crypto_mode) { CryptoMode crypto_mode) {
switch (crypto_mode) { switch (crypto_mode) {
@@ -114,6 +168,11 @@ Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
return {error::INVALID_ARGUMENT, "Content IV size must match."}; 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; EcmPayload ecm_payload;
EcmMetaData* meta_data = ecm_payload.mutable_meta_data(); 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(); const std::string serialized_ecm_payload = ecm_payload.SerializeAsString();
SignedEcmPayload signed_ecm_payload; SignedEcmPayload signed_ecm_payload;
signed_ecm_payload.set_serialized_payload(serialized_ecm_payload); signed_ecm_payload.set_serialized_payload(serialized_ecm_payload);

View File

@@ -7,6 +7,7 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h" #include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -86,6 +87,35 @@ class EcmSerializerV3Test : public testing::Test {
EXPECT_EQ(ecm_key_data.wrapped_key_iv(), key_info.wrapped_key_iv); 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_; EcmSerializerParams serializer_params_;
}; };
@@ -407,6 +437,142 @@ TEST_F(EcmSerializerV3Test, GenerateSignatureSuccess) {
signed_payload.signature())); 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
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -8,6 +8,10 @@
#include "media_cas_packager_sdk/internal/ecm.h" #include "media_cas_packager_sdk/internal/ecm.h"
#include <cstddef>
#include <cstdint>
#include <vector>
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
@@ -32,6 +36,7 @@ constexpr char kTrackTypeSD[] = "SD";
constexpr char kTrackTypeHD[] = "HD"; constexpr char kTrackTypeHD[] = "HD";
constexpr char kWrappedKeyIv[] = "wrapped_key_iv.."; constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
constexpr char kEntitlementKeyId[] = "entitlement_Mock"; constexpr char kEntitlementKeyId[] = "entitlement_Mock";
constexpr char kEntitlementKeyValue[] = "key__value.key__value.key__value";
constexpr char kWrappedKeyValue[] = "MockMockMockMock"; constexpr char kWrappedKeyValue[] = "MockMockMockMock";
constexpr size_t kEcmVersionIndex = 2; constexpr size_t kEcmVersionIndex = 2;
@@ -96,9 +101,11 @@ class EcmTest : public testing::Test {
params_one_key_.key_rotation_enabled = false; params_one_key_.key_rotation_enabled = false;
params_two_keys_.key_rotation_enabled = true; 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"}; "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"}; "key__value.key__value.key__value"};
} }
@@ -121,7 +128,9 @@ TEST_F(EcmTest, GenerateEcmNotInitialized) {
EntitledKeyInfo key1; EntitledKeyInfo key1;
std::string ecm_data; std::string ecm_data;
EXPECT_EQ(error::INTERNAL, EXPECT_EQ(error::INTERNAL,
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data) ecm_gen
.GenerateSingleKeyEcm(&key1, kTrackTypeSD, /*group_ids=*/{},
&ecm_data)
.error_code()); .error_code());
} }
@@ -130,9 +139,10 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) {
EntitledKeyInfo key1; EntitledKeyInfo key1;
EntitledKeyInfo key2; EntitledKeyInfo key2;
std::string ecm_data; std::string ecm_data;
EXPECT_EQ( EXPECT_EQ(error::INTERNAL, ecm_gen
error::INTERNAL, .GenerateEcm(&key1, &key2, kTrackTypeSD,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code()); /*group_ids=*/{}, &ecm_data)
.error_code());
} }
TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) { TEST_F(EcmTest, InitSucceedInjectedEntitlementSingle) {
@@ -179,7 +189,8 @@ TEST_F(EcmTest, InitIvSize16x16OK) {
} }
class EcmInitWithValidCasIdTest : public EcmTest, class EcmInitWithValidCasIdTest : public EcmTest,
public testing::WithParamInterface<uint16_t> {}; public testing::WithParamInterface<uint16_t> {
};
TEST_P(EcmInitWithValidCasIdTest, InitWithValidCasIdSucceed) { TEST_P(EcmInitWithValidCasIdTest, InitWithValidCasIdSucceed) {
params_simple_.cas_id = GetParam(); params_simple_.cas_id = GetParam();
Ecm ecm_gen; Ecm ecm_gen;
@@ -191,9 +202,9 @@ INSTANTIATE_TEST_SUITE_P(EcmInitWithValidCasIdTest, EcmInitWithValidCasIdTest,
kWidevineNewSystemIdLowerBound, 0x56C5, kWidevineNewSystemIdLowerBound, 0x56C5,
kWidevineNewSystemIdUpperBound)); kWidevineNewSystemIdUpperBound));
class EcmInitWithInvalidCasIdTest : public EcmTest, class EcmInitWithInvalidCasIdTest
public testing::WithParamInterface<uint16_t> { : public EcmTest,
}; public testing::WithParamInterface<uint16_t> {};
TEST_P(EcmInitWithInvalidCasIdTest, InitWithInvalidCasIdFail) { TEST_P(EcmInitWithInvalidCasIdTest, InitWithInvalidCasIdFail) {
params_simple_.cas_id = GetParam(); params_simple_.cas_id = GetParam();
Ecm ecm_gen; Ecm ecm_gen;
@@ -219,8 +230,8 @@ TEST_P(EcmSerializerVersionTest, EcmSerializerOk) {
ASSERT_OK(ecm_gen.Initialize(params, {injected_entitlement_one_})); ASSERT_OK(ecm_gen.Initialize(params, {injected_entitlement_one_}));
std::string ecm; std::string ecm;
ASSERT_OK( ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD,
ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD, &ecm)); /*group_ids=*/{}, &ecm));
ASSERT_GT(ecm.size(), kEcmVersionIndex); ASSERT_GT(ecm.size(), kEcmVersionIndex);
EXPECT_EQ(ecm[kEcmVersionIndex], EXPECT_EQ(ecm[kEcmVersionIndex],
params.ecm_version == EcmVersion::kV2 ? 2 : 3); params.ecm_version == EcmVersion::kV2 ? 2 : 3);
@@ -237,8 +248,10 @@ TEST_F(EcmTest, GenerateWithBadTrackType) {
EntitledKeyInfo key1 = valid1_iv_16_8_; EntitledKeyInfo key1 = valid1_iv_16_8_;
EntitledKeyInfo key2 = valid2_iv_16_8_; EntitledKeyInfo key2 = valid2_iv_16_8_;
std::string ecm; std::string ecm;
EXPECT_EQ(error::INTERNAL, EXPECT_EQ(
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code()); error::INTERNAL,
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, /*group_ids=*/{}, &ecm)
.error_code());
} }
class MockEcmSerializer : public EcmSerializer { class MockEcmSerializer : public EcmSerializer {
@@ -252,6 +265,7 @@ class MockEcmSerializer : public EcmSerializer {
MOCK_METHOD(EcmSerializerVersion, Version, (), (const, override)); MOCK_METHOD(EcmSerializerVersion, Version, (), (const, override));
}; };
// For easier test by setting mock ecm serializer and fixed wrapped key value.
class FakeEcm : public Ecm { class FakeEcm : public Ecm {
public: public:
FakeEcm() = default; FakeEcm() = default;
@@ -262,18 +276,16 @@ class FakeEcm : public Ecm {
return Ecm::SetEcmSerializer(std::move(ecm_serializer)); return Ecm::SetEcmSerializer(std::move(ecm_serializer));
} }
Status WrapEntitledKeys( Status WrapKey(const std::string& wrapping_key,
const std::string& track_type, const std::string& wrapping_iv, const std::string& key_value,
const std::vector<EntitledKeyInfo*>& keys) const override { std::string* wrapped_key) const override {
for (auto entitled_key : keys) { *wrapped_key = kWrappedKeyValue;
entitled_key->entitlement_key_id = kEntitlementKeyId;
entitled_key->wrapped_key_value = kWrappedKeyValue;
}
return OkStatus(); 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) && return (a.entitlement_key_id == b.entitlement_key_id) &&
(a.content_key_id == b.content_key_id) && (a.content_key_id == b.content_key_id) &&
(a.wrapped_key_value == b.wrapped_key_value) && (a.wrapped_key_value == b.wrapped_key_value) &&
@@ -281,12 +293,29 @@ bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) {
(a.content_iv == b.content_iv); (a.content_iv == b.content_iv);
} }
bool IsEcmSerializerGroupKeyInfoEq(
const std::vector<EcmSerializerGroupKeyInfo>& a,
const std::vector<EcmSerializerGroupKeyInfo>& 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, "") { MATCHER_P(EcmSerializerParamsEq, expected, "") {
return (arg.cas_id == expected.cas_id) && return (arg.cas_id == expected.cas_id) &&
(arg.crypto_mode == expected.crypto_mode) && (arg.crypto_mode == expected.crypto_mode) &&
(arg.age_restriction == expected.age_restriction) && (arg.age_restriction == expected.age_restriction) &&
IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) && 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) { TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
@@ -302,10 +331,12 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
expected_params.cas_id = kWvCasCaSystemId; expected_params.cas_id = kWvCasCaSystemId;
expected_params.age_restriction = 0; expected_params.age_restriction = 0;
expected_params.crypto_mode = CryptoMode::kAesCtr; expected_params.crypto_mode = CryptoMode::kAesCtr;
expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue, expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id,
key1.wrapped_key_iv, key1.content_iv}; kWrappedKeyValue, key1.wrapped_key_iv,
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue, key1.content_iv};
key2.wrapped_key_iv, key2.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"; std::string expected_serialized_ecm = "expected_serialized_ecm";
EXPECT_CALL(*ecm_serializer_pointer, EXPECT_CALL(*ecm_serializer_pointer,
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull())) SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
@@ -313,8 +344,8 @@ TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
DoAll(SetArgPointee<1>(expected_serialized_ecm), Return(OkStatus()))); DoAll(SetArgPointee<1>(expected_serialized_ecm), Return(OkStatus())));
std::string actual_generated_ecm; std::string actual_generated_ecm;
ASSERT_OK( ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, /*group_ids=*/{},
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &actual_generated_ecm)); &actual_generated_ecm));
EXPECT_EQ(actual_generated_ecm, expected_serialized_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.age_restriction = params.age_restriction;
expected_params.crypto_mode = params.crypto_mode; expected_params.crypto_mode = params.crypto_mode;
expected_params.ecc_private_signing_key = params.ecc_private_signing_key; expected_params.ecc_private_signing_key = params.ecc_private_signing_key;
expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue, expected_params.even_key = {injected_entitlement_one_.key_id, key1.key_id,
key1.wrapped_key_iv, key1.content_iv}; kWrappedKeyValue, key1.wrapped_key_iv,
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue, key1.content_iv};
key2.wrapped_key_iv, key2.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, EXPECT_CALL(*ecm_serializer_pointer,
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull())) SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
.WillOnce(Return(OkStatus())); .WillOnce(Return(OkStatus()));
std::string generated_ecm; 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) { TEST_F(EcmTest, GenerateEcmSerializeErrorOK) {
@@ -365,10 +399,88 @@ TEST_F(EcmTest, GenerateEcmSerializeErrorOK) {
std::string ecm; std::string ecm;
EXPECT_EQ(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_, EXPECT_EQ(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
kTrackTypeSD, &ecm), kTrackTypeSD, /*group_ids=*/{}, &ecm),
expected_status); expected_status);
} }
TEST_F(EcmTest, GenerateEcmWithGroupKeysSuccess) {
FakeEcm ecm_gen;
EcmInitParameters params;
std::vector<std::string> 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<MockEcmSerializer>();
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 {}; class EcmTsPacketTest : public ::testing::Test {};
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) { TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {

View File

@@ -9,15 +9,19 @@
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstring> #include <cstring>
#include "glog/logging.h" #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/memory/memory.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include "common/crypto_util.h" #include "common/crypto_util.h"
#include "common/random_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/ecmg_constants.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h" #include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
@@ -94,14 +98,54 @@ Status ProcessPrivateParameters(const char* const request, uint16_t param_type,
absl::StrCat("Invalid parameter length ", param_length, absl::StrCat("Invalid parameter length ", param_length,
" for parameter type ", param_type)); " for parameter type ", param_type));
} }
params->entitlement_comb.emplace_back(); EntitlementKeyInfo entitlement_key;
EntitlementIdKeyComb* combination = &params->entitlement_comb.back(); entitlement_key.key_id.assign(request + *offset,
combination->key_id = kEntitlementKeyIdSizeBytes);
std::string(request + *offset, kEntitlementKeyIdSizeBytes); entitlement_key.key_value.assign(
*offset += kEntitlementKeyIdSizeBytes; request + *offset + kEntitlementKeyIdSizeBytes,
combination->key_value = kEntitlementKeyValueSizeBytes);
std::string(request + *offset, kEntitlementKeyValueSizeBytes); // If a key has been already received (without group id), then this is an
*offset += kEntitlementKeyValueSizeBytes; // 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; break;
} }
case FINGERPRINTING_CONTROL: 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; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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, 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); message_length);
uint16_t total_param_length = *message_length - 5; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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, void BuildStreamError(uint16_t channel_id, uint16_t stream_id,
const std::string& error_info, char* message, uint16_t error_status, const std::string& error_info,
size_t* message_length) { char* message, size_t* message_length) {
DCHECK(message); DCHECK(message);
DCHECK(message_length); DCHECK(message_length);
simulcrypt_util::BuildMessageHeader( 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; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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) { 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); message_length);
uint16_t total_param_length = *message_length - 5; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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, 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); message_length);
uint16_t total_param_length = *message_length - 5; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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<const uint8_t> ecm_datagram, char* message, absl::Span<const uint8_t> ecm_datagram, char* message,
size_t* message_length) { size_t* message_length) {
DCHECK(message); 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); ecm_datagram.size(), message, message_length);
uint16_t total_param_length = *message_length - 5; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); 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<std::string>& content_ivs, Status CheckReceivedContentIv(const std::vector<std::string>& content_ivs,
@@ -460,32 +536,6 @@ Status CheckReceivedContentIv(const std::vector<std::string>& content_ivs,
return OkStatus(); return OkStatus();
} }
std::vector<EntitlementIdKeyComb> ConvertEntitlementKeyInfo(
const std::vector<EntitlementKeyInfo>& fetched_entitlements) {
std::vector<EntitlementIdKeyComb> 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 } // namespace
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config) EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
@@ -496,8 +546,9 @@ EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
ecmg_config_ = ecmg_config; ecmg_config_ = ecmg_config;
} }
void EcmgClientHandler::HandleRequest(const char* const request, char* response, size_t EcmgClientHandler::HandleRequest(const char* const request,
size_t* response_length) { char* response,
size_t* response_length) {
DCHECK(request); DCHECK(request);
DCHECK(response); 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|. // 'offset' is used to track where we are within |request|.
size_t offset = 0; size_t offset = 0;
memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE); 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; offset += PROTOCOL_VERSION_SIZE;
BigEndianToHost16(&request_type, request + offset); BigEndianToHost16(&request_type, request + offset);
offset += MESSAGE_TYPE_SIZE; offset += MESSAGE_TYPE_SIZE;
BigEndianToHost16(&request_length, request + offset); BigEndianToHost16(&request_length, request + offset);
offset += MESSAGE_LENGTH_SIZE; 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; EcmgParameters params;
const bool should_sdk_process_access_criteria = const bool should_sdk_process_access_criteria =
custom_ac_processor_ == nullptr; custom_ac_processor_ == nullptr;
@@ -526,7 +579,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
LOG(ERROR) << status.ToString(); LOG(ERROR) << status.ToString();
BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status), BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status),
status.error_message(), response, response_length); status.error_message(), response, response_length);
return; return total_request_length;
} }
switch (request_type) { switch (request_type) {
case ECMG_CHANNEL_SETUP: case ECMG_CHANNEL_SETUP:
@@ -549,7 +602,14 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
break; break;
case ECMG_CW_PROVISION: case ECMG_CW_PROVISION:
if (!should_sdk_process_access_criteria) { 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); HandleCwProvision(params, response, response_length);
break; break;
@@ -564,6 +624,7 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
response, response_length); response, response_length);
break; break;
} }
return total_request_length;
} }
void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
@@ -571,6 +632,9 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
size_t* response_length) { size_t* response_length) {
DCHECK(response); DCHECK(response);
DCHECK(response_length); 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. // There is always one (and only one) channel per TCP connection.
if (channel_id_set_) { if (channel_id_set_) {
BuildChannelError(params.ecm_channel_id, INVALID_MESSAGE, "", response, 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); BuildChannelStatus(channel_id_, ecmg_config_, response, response_length);
VLOG(1) << "Channel " << channel_id_ << " opened";
} }
void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
@@ -611,6 +676,8 @@ void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params,
size_t* response_length) const { size_t* response_length) const {
DCHECK(response); DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "ChannelTest request. ECM_channel_id: " << params.ecm_channel_id;
if (!channel_id_set_ || 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, "", BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
response, response_length); response, response_length);
@@ -624,6 +691,8 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
size_t* response_length) { size_t* response_length) {
DCHECK(response); DCHECK(response);
DCHECK(response_length); DCHECK(response_length);
VLOG(3) << "ChannelClose request. ECM_channel_id: " << params.ecm_channel_id;
if (!channel_id_set_ || 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, "", BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
response, response_length); response, response_length);
@@ -632,11 +701,14 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params,
channel_id_set_ = false; channel_id_set_ = false;
streams_info_.clear(); streams_info_.clear();
*response_length = 0; *response_length = 0;
VLOG(1) << "Channel " << channel_id_ << " closed";
} }
void EcmgClientHandler::HandleChannelError(const EcmgParameters& params, void EcmgClientHandler::HandleChannelError(const EcmgParameters& params,
char* response, char* response,
size_t* response_length) const { 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "",
response, response_length); response, response_length);
@@ -659,6 +731,11 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
size_t* response_length) { size_t* response_length) {
DCHECK(response); DCHECK(response);
DCHECK(response_length); 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, 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. // Try to fetch entitlement keys using custom key fetcher if none is received.
std::vector<EntitlementIdKeyComb>& entitlements = if (custom_key_fetcher_ != nullptr) {
streams_info_[params.ecm_stream_id]->entitlement_comb; auto& entitlements = streams_info_[params.ecm_stream_id]->entitlement_keys;
if (entitlements.empty() && custom_key_fetcher_ != nullptr) { if (entitlements.empty()) {
const std::vector<EntitlementKeyInfo>& fetched_entitlements = entitlements = custom_key_fetcher_(params.ecm_channel_id,
custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id, params.ecm_stream_id, params.ecm_id);
params.ecm_id); }
const std::vector<EntitlementIdKeyComb>& converted_entitlements =
ConvertEntitlementKeyInfo(fetched_entitlements);
entitlements.assign(converted_entitlements.begin(),
converted_entitlements.end());
} }
BuildStreamStatus(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, ecmg_config_->access_criteria_transfer_mode, response,
response_length); response_length);
VLOG(1) << "Stream " << params.ecm_stream_id
<< " opened. Number of streams open: " << streams_info_.size();
} }
void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
@@ -711,6 +786,9 @@ void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params,
size_t* response_length) const { size_t* response_length) const {
DCHECK(response); DCHECK(response);
DCHECK(response_length); 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
@@ -734,6 +812,9 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
size_t* response_length) { size_t* response_length) {
DCHECK(response); DCHECK(response);
DCHECK(response_length); 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
@@ -749,11 +830,16 @@ void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params,
streams_info_.erase(params.ecm_stream_id); streams_info_.erase(params.ecm_stream_id);
BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id, BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id,
response, response_length); response, response_length);
VLOG(1) << "Stream " << params.ecm_stream_id
<< " closed. Number of streams open: " << streams_info_.size();
} }
void EcmgClientHandler::HandleStreamError(const EcmgParameters& params, void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
char* response, char* response,
size_t* response_length) const { 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) { if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) {
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response,
@@ -778,11 +864,16 @@ void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
*response_length = 0; *response_length = 0;
} }
void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor( Status EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
EcmgParameters& params) const { EcmgParameters& params) const {
DCHECK(custom_ac_processor_ != nullptr); DCHECK(custom_ac_processor_ != nullptr);
const EcmgCustomParameters& custom_params = custom_ac_processor_( EcmgCustomParameters custom_params;
params.ecm_channel_id, params.ecm_stream_id, params.access_criteria); 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|. // Load results to |params|.
if (custom_params.age_restriction >= 0) { if (custom_params.age_restriction >= 0) {
params.age_restriction = custom_params.age_restriction; params.age_restriction = custom_params.age_restriction;
@@ -794,10 +885,7 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
params.content_ivs = custom_params.content_ivs; params.content_ivs = custom_params.content_ivs;
} }
if (!custom_params.entitlement_keys.empty()) { if (!custom_params.entitlement_keys.empty()) {
const std::vector<EntitlementIdKeyComb>& converted_entitlements = params.entitlement_keys = custom_params.entitlement_keys;
ConvertEntitlementKeyInfo(custom_params.entitlement_keys);
params.entitlement_comb.assign(converted_entitlements.begin(),
converted_entitlements.end());
} }
if (!custom_params.fingerprinting_control.empty()) { if (!custom_params.fingerprinting_control.empty()) {
params.fingerprinting_control = custom_params.fingerprinting_control; params.fingerprinting_control = custom_params.fingerprinting_control;
@@ -805,11 +893,17 @@ void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
if (!custom_params.service_blocking_groups.empty()) { if (!custom_params.service_blocking_groups.empty()) {
params.service_blocking_groups = custom_params.service_blocking_groups; params.service_blocking_groups = custom_params.service_blocking_groups;
} }
return OkStatus();
} }
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params, void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
char* response, char* response,
size_t* response_length) { 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);
DCHECK(response_length); DCHECK(response_length);
if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { 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."}; return {error::INVALID_ARGUMENT, "Age restriction too large."};
} }
age_restriction_ = params.age_restriction; age_restriction_ = params.age_restriction;
VLOG(1) << "Channel age restriction has been set to: " << age_restriction_;
} }
if (!params.crypto_mode.empty()) { if (!params.crypto_mode.empty()) {
if (!StringToCryptoMode(params.crypto_mode, &ecmg_config_->crypto_mode)) { if (!StringToCryptoMode(params.crypto_mode, &ecmg_config_->crypto_mode)) {
return {error::INVALID_ARGUMENT, return {error::INVALID_ARGUMENT,
absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")}; 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()) { if (!params.content_ivs.empty()) {
Status status = CheckReceivedContentIv( Status status = CheckReceivedContentIv(
@@ -896,6 +992,8 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters(
return status; return status;
} }
content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end()); 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(); return OkStatus();
} }
@@ -916,6 +1014,9 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
if (stream_info->age_restriction != params.age_restriction) { if (stream_info->age_restriction != params.age_restriction) {
stream_info->age_restriction = params.age_restriction; stream_info->age_restriction = params.age_restriction;
invalidate_ecm_gen = true; 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) { if (stream_info->crypto_mode != new_crypto_mode) {
stream_info->crypto_mode = new_crypto_mode; stream_info->crypto_mode = new_crypto_mode;
invalidate_ecm_gen = true; 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(), stream_info->content_ivs.assign(params.content_ivs.begin(),
params.content_ivs.end()); 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() && if (!params.entitlement_keys.empty() &&
stream_info->entitlement_comb != params.entitlement_comb) { stream_info->entitlement_keys != params.entitlement_keys) {
stream_info->entitlement_comb.assign(params.entitlement_comb.begin(), stream_info->entitlement_keys = params.entitlement_keys;
params.entitlement_comb.end());
invalidate_ecm_gen = true; 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) { if (invalidate_ecm_gen) {
@@ -967,14 +1075,22 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
if (stream_info->content_ivs.empty()) { if (stream_info->content_ivs.empty()) {
return {error::NOT_FOUND, "Content iv not specified."}; 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<std::string, int> 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."}; return {error::NOT_FOUND, "Entitlement key id comb not specified."};
} }
if (stream_info->entitlement_comb.size() != for (const auto& group_id_count : group_id_to_key_count) {
ecmg_config_->number_of_content_keys) { if (group_id_count.second != ecmg_config_->number_of_content_keys) {
return {error::NOT_FOUND, return {error::NOT_FOUND,
"Number of injected entitlement keys must equal to number of " "Number of injected entitlement keys must equal to number of "
"content keys per ecm."}; "content keys per ecm."};
}
} }
bool key_rotation = ecmg_config_->number_of_content_keys > 1; 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 = ecm_init_params.ecc_private_signing_key =
ecmg_config_->ecc_private_signing_key; ecmg_config_->ecc_private_signing_key;
std::vector<EntitlementKeyInfo> entitlements; // Override track type. This Simulcrypt ECMg implementation assumes only one
entitlements.reserve(stream_info->entitlement_comb.size()); // track type -- all entitlement keys passed in will be used to generate ECM.
for (size_t i = 0; i < stream_info->entitlement_comb.size(); i++) { // It makes a copy to avoid changing the original one as the original one may
entitlements.emplace_back(); // be used to detect if there is change or not.
EntitlementKeyInfo* entitlement = &entitlements.back(); std::vector<EntitlementKeyInfo> modified_entitlement_keys =
entitlement->track_type = kDefaultTrackType; stream_info->entitlement_keys;
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true; for (auto& entitlement_key : modified_entitlement_keys) {
entitlement->key_id = stream_info->entitlement_comb[i].key_id; entitlement_key.track_type = kDefaultTrackType;
entitlement->key_value = stream_info->entitlement_comb[i].key_value;
} }
stream_info->ecm = CreateEcmInstance(); 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, 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.device_groups.empty() ? nullptr
: &service_blocking_params); : &service_blocking_params);
// Prepare group ids. All group ids of entitlement keys received will be used.
absl::flat_hash_set<std::string> 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<std::string> group_ids(group_id_set.begin(), group_id_set.end());
Status status; Status status;
std::string serialized_ecm; std::string serialized_ecm;
if (key_count > 1) { if (key_count > 1) {
status = stream_info->ecm->GenerateEcm(&keys[0], &keys[1], status = stream_info->ecm->GenerateEcm(
kDefaultTrackType, &serialized_ecm); &keys[0], &keys[1], kDefaultTrackType, group_ids, &serialized_ecm);
} else { } else {
status = stream_info->ecm->GenerateSingleKeyEcm(&keys[0], kDefaultTrackType, status = stream_info->ecm->GenerateSingleKeyEcm(&keys[0], kDefaultTrackType,
&serialized_ecm); group_ids, &serialized_ecm);
} }
if (!status.ok()) { if (!status.ok()) {
return status; return status;

View File

@@ -10,6 +10,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CLIENT_HANDLER_H_
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -27,23 +28,10 @@ namespace cas {
// A struct that represent a CP_CW_Combination. // A struct that represent a CP_CW_Combination.
struct EcmgCpCwCombination { struct EcmgCpCwCombination {
uint16_t cp; // crypto period uint16_t cp; // crypto period
std::string cw; // control word 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. // A struct that is used to hold all possible params for a ECMG request.
struct EcmgParameters { struct EcmgParameters {
std::vector<EcmgCpCwCombination> cp_cw_combinations; // Size is 1 or 2. std::vector<EcmgCpCwCombination> 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. int age_restriction = -1; // Negative value to indidate the field is not set.
std::string crypto_mode; std::string crypto_mode;
std::vector<std::string> content_ivs; // 8 or 16 bytes, one for each key. std::vector<std::string> content_ivs; // 8 or 16 bytes, one for each key.
std::vector<EntitlementIdKeyComb> entitlement_comb; std::vector<EntitlementKeyInfo> entitlement_keys;
std::string access_criteria; std::string access_criteria;
std::string fingerprinting_control; std::string fingerprinting_control;
std::vector<std::string> service_blocking_groups; std::vector<std::string> service_blocking_groups;
@@ -73,7 +61,7 @@ struct EcmgStreamInfo {
CryptoMode crypto_mode; CryptoMode crypto_mode;
// 8 or 16 bytes, one for each key. // 8 or 16 bytes, one for each key.
std::vector<std::string> content_ivs; std::vector<std::string> content_ivs;
std::vector<EntitlementIdKeyComb> entitlement_comb; std::vector<EntitlementKeyInfo> entitlement_keys;
std::unique_ptr<Ecm> ecm; std::unique_ptr<Ecm> ecm;
uint8_t age_restriction; uint8_t age_restriction;
}; };
@@ -90,8 +78,9 @@ class EcmgClientHandler {
// Handle a |request| from the client. // Handle a |request| from the client.
// If any response is generated, it would returned via |response| // If any response is generated, it would returned via |response|
// and |response_length|. // and |response_length|.
virtual void HandleRequest(const char* const request, char* response, // Returns length of |request| that has been processed.
size_t* response_length); 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 // Sets the custom entitlement key fetching function used by ECMG to fetch
// entitlement keys. If entitlement key information is not received in // entitlement keys. If entitlement key information is not received in
@@ -145,7 +134,7 @@ class EcmgClientHandler {
size_t* response_length) const; size_t* response_length) const;
void HandleCwProvision(const EcmgParameters& params, char* response, void HandleCwProvision(const EcmgParameters& params, char* response,
size_t* response_length); size_t* response_length);
void UpdateParamsWithCustomAccessCriteriaProcessor( Status UpdateParamsWithCustomAccessCriteriaProcessor(
EcmgParameters& params) const; EcmgParameters& params) const;
// Update channel level private parameters using |params|. // Update channel level private parameters using |params|.

View File

@@ -12,6 +12,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <cstdint>
#include <memory> #include <memory>
#include "testing/gmock.h" #include "testing/gmock.h"
@@ -20,6 +21,7 @@
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include "common/status.h"
#include "example/test_ecmg_messages.h" #include "example/test_ecmg_messages.h"
#include "media_cas_packager_sdk/internal/ecm.h" #include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.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::AddUint32Param;
using simulcrypt_util::AddUint8Param; using simulcrypt_util::AddUint8Param;
using simulcrypt_util::BuildMessageHeader; using simulcrypt_util::BuildMessageHeader;
using ::testing::_;
using ::testing::ByMove; using ::testing::ByMove;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray; using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::Return; using ::testing::Return;
constexpr size_t kBufferSize = 1024; constexpr size_t kBufferSize = 1024;
@@ -69,28 +75,29 @@ constexpr int kExpectedEcmResponseLength = sizeof(kTestEcmgEcmResponse);
constexpr char kFingerprintingControl[] = "control"; constexpr char kFingerprintingControl[] = "control";
constexpr char kServiceBlockingGroup1[] = "group1"; constexpr char kServiceBlockingGroup1[] = "group1";
constexpr char kServiceBlockingGroup2[] = "group2"; constexpr char kServiceBlockingGroup2[] = "group2";
constexpr char kEntitlementGroupId[] = "GroupId";
std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc( std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) { uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) {
return {{kTrackTypesSD, /*is_even_key*/ false, kEntitlementKeyIdOdd, return {{kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/false,
kEntitlementKeyValueOdd}, kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
{kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdEven, {kTrackTypesSD, /*group_id=*/"", /*is_even_key=*/true,
kEntitlementKeyValueEven}}; kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
} }
EcmgCustomParameters FakeCustomAcProcessorFunc( Status FakeCustomAcProcessorFunc(uint16_t /*channel_id*/,
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*stream_id*/,
const std::string& access_criteria) { const std::string& access_criteria,
EcmgCustomParameters params; EcmgCustomParameters& params) {
params.age_restriction = access_criteria.empty() ? 0 : 3; params.age_restriction = access_criteria.empty() ? 0 : 3;
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd}; params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
params.crypto_mode = kCryptoMode; params.crypto_mode = kCryptoMode;
params.entitlement_keys = {{kTrackTypesSD, /*is_even_key*/ false, params.entitlement_keys = {
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd}, {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ false,
{kTrackTypesSD, /*is_even_key*/ true, kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
kEntitlementKeyIdEven, kEntitlementKeyValueEven}}; {kTrackTypesSD, /*group_id=*/"", /*is_even_key*/ true,
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
return params; return OkStatus();
} }
class MockEcmgClientHandler : public EcmgClientHandler { class MockEcmgClientHandler : public EcmgClientHandler {
@@ -178,18 +185,21 @@ class EcmgClientHandlerTest : public ::testing::Test {
AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length); AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length);
} }
if (!crypto_mode.empty()) { if (!crypto_mode.empty()) {
AddParam(CRYPTO_MODE, reinterpret_cast<const uint8_t*>(crypto_mode.c_str()), AddParam(CRYPTO_MODE,
reinterpret_cast<const uint8_t*>(crypto_mode.c_str()),
crypto_mode.size(), message, message_length); crypto_mode.size(), message, message_length);
} }
uint16_t total_param_length = *message_length - 5; uint16_t total_param_length = *message_length - 5;
Host16ToBigEndian(message + 3, &total_param_length); Host16ToBigEndian(message + 3, &total_param_length);
} }
void BuildStreamSetupRequest(uint16_t channel_id, uint16_t stream_id, void BuildStreamSetupRequest(
uint16_t ecm_id, uint16_t nominal_CP_duration, uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id,
const std::vector<std::string>& entitlements, uint16_t nominal_CP_duration,
const std::vector<std::string>& content_ivs, const std::vector<std::string>& entitlements,
char* message, size_t* message_length) { const std::vector<std::string>& group_entitlements,
const std::vector<std::string>& content_ivs, char* message,
size_t* message_length) {
EXPECT_TRUE(message != nullptr); EXPECT_TRUE(message != nullptr);
EXPECT_TRUE(message_length != nullptr); EXPECT_TRUE(message_length != nullptr);
BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message, BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message,
@@ -207,9 +217,17 @@ class EcmgClientHandlerTest : public ::testing::Test {
entitlement.size(), message, message_length); entitlement.size(), message, message_length);
} }
} }
if (!group_entitlements.empty()) {
for (const auto& entitlement : group_entitlements) {
AddParam(ENTITLEMENT_ID_KEY_GROUP_COMBINATION,
reinterpret_cast<const uint8_t*>(entitlement.c_str()),
entitlement.size(), message, message_length);
}
}
if (!content_ivs.empty()) { if (!content_ivs.empty()) {
for (auto& content_iv : content_ivs) { for (auto& content_iv : content_ivs) {
AddParam(CONTENT_IV, reinterpret_cast<const uint8_t*>(content_iv.c_str()), AddParam(CONTENT_IV,
reinterpret_cast<const uint8_t*>(content_iv.c_str()),
content_iv.size(), message, message_length); content_iv.size(), message, message_length);
} }
} }
@@ -253,8 +271,8 @@ class EcmgClientHandlerTest : public ::testing::Test {
Host16ToBigEndian(message + 3, &total_param_length); Host16ToBigEndian(message + 3, &total_param_length);
} }
void CheckChannelError(uint16_t expected_error_code, const char* const response, void CheckChannelError(uint16_t expected_error_code,
size_t response_length) { const char* const response, size_t response_length) {
EXPECT_LE(sizeof(kTestChannelErrorResponse), response_length); EXPECT_LE(sizeof(kTestChannelErrorResponse), response_length);
// Message version and message type // Message version and message type
EXPECT_EQ(0, memcmp(kTestChannelErrorResponse, response, 3)); EXPECT_EQ(0, memcmp(kTestChannelErrorResponse, response, 3));
@@ -265,8 +283,8 @@ class EcmgClientHandlerTest : public ::testing::Test {
EXPECT_EQ(expected_error_code & 0xff, response[16]); EXPECT_EQ(expected_error_code & 0xff, response[16]);
} }
void CheckStreamError(uint16_t expected_error_code, const char* const response, void CheckStreamError(uint16_t expected_error_code,
size_t response_length) { const char* const response, size_t response_length) {
EXPECT_LE(sizeof(kTestStreamErrorResponse), response_length); EXPECT_LE(sizeof(kTestStreamErrorResponse), response_length);
// Message version and message type // Message version and message type
EXPECT_EQ(0, memcmp(kTestStreamErrorResponse, response, 3)); EXPECT_EQ(0, memcmp(kTestStreamErrorResponse, response, 3));
@@ -374,7 +392,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kChannelId, kStreamId, kEcmId, kNominalCpDuration,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
@@ -383,7 +402,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration, kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
@@ -414,7 +434,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
// No entitlement keys are injected. // No entitlement keys are injected.
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
/*entitlements=*/{}, /*entitlements=*/{}, /*group_entitlements=*/{},
{kContentKeyIvEven, kContentKeyIvOdd}, request_, {kContentKeyIvEven, kContentKeyIvOdd}, request_,
&request_len_); &request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
@@ -482,7 +502,8 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kChannelId, kStreamId, kEcmId, kNominalCpDuration,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
@@ -614,7 +635,8 @@ TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
0, kStreamId, kEcmId, kNominalCpDuration, 0, kStreamId, kEcmId, kNominalCpDuration,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, 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_)); EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
/*entitlements*/ {}, /*entitlements*/ {}, /*group_entitlements=*/{},
{kContentKeyEven, kContentKeyEven}, request_, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_); &request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
@@ -668,7 +690,8 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
BuildStreamSetupRequest( BuildStreamSetupRequest(
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kChannelId, kStreamId, kEcmId, kNominalCpDuration,
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)}, {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
@@ -694,7 +717,8 @@ TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd), absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)}, absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
{kContentKeyEven, kContentKeyEven}, request_, &request_len_); /*group_entitlements=*/{}, {kContentKeyEven, kContentKeyEven}, request_,
&request_len_);
handler_->HandleRequest(request_, response_, &response_len_); handler_->HandleRequest(request_, response_, &response_len_);
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
@@ -766,7 +790,7 @@ TEST_F(EcmgClientHandlerTest, NewCasIdEmbeddedInEcmSuccess) {
ASSERT_EQ(response_len_, sizeof(kTestEcmgEcmResponse)); ASSERT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
uint16_t actual_cas_id = (static_cast<uint8_t>(response_[35]) << 8) | uint16_t actual_cas_id = (static_cast<uint8_t>(response_[35]) << 8) |
static_cast<uint8_t>(response_[36]); static_cast<uint8_t>(response_[36]);
EXPECT_EQ(actual_cas_id, expected_cas_id); EXPECT_EQ(actual_cas_id, expected_cas_id);
} }
@@ -806,10 +830,20 @@ class MockEcm : public Ecm {
public: public:
MockEcm() = default; MockEcm() = default;
~MockEcm() override = default; ~MockEcm() override = default;
MOCK_METHOD(Status, Initialize,
(const EcmInitParameters&,
const std::vector<EntitlementKeyInfo>&),
(override));
MOCK_METHOD(void, SetFingerprinting, (const EcmFingerprintingParams*), MOCK_METHOD(void, SetFingerprinting, (const EcmFingerprintingParams*),
(override)); (override));
MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*), MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*),
(override)); (override));
MOCK_METHOD(Status, GenerateEcm,
(EntitledKeyInfo * even_key, EntitledKeyInfo* odd_key,
const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm),
(const, override));
}; };
EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id, EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id,
@@ -895,6 +929,149 @@ TEST_F(EcmgClientHandlerTest, SetServiceBlockingViaAceessCriteriaSuccess) {
handler_->HandleRequest(request_, response_, &response_len_); 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>();
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<EntitlementKeyInfo> 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>();
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>();
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
} // namespace cas } // namespace cas
} // namespace widevine } // namespace widevine

View File

@@ -68,6 +68,7 @@
#define ENTITLEMENT_ID_KEY_COMBINATION (0x8004) #define ENTITLEMENT_ID_KEY_COMBINATION (0x8004)
#define FINGERPRINTING_CONTROL (0x8005) #define FINGERPRINTING_CONTROL (0x8005)
#define SERVICE_BLOCKING_GROUP (0x8006) #define SERVICE_BLOCKING_GROUP (0x8006)
#define ENTITLEMENT_ID_KEY_GROUP_COMBINATION (0x8007)
// ECMG protocol error values. // ECMG protocol error values.
#define INVALID_MESSAGE (0x0001) #define INVALID_MESSAGE (0x0001)

View File

@@ -9,6 +9,7 @@
#include "media_cas_packager_sdk/internal/emm.h" #include "media_cas_packager_sdk/internal/emm.h"
#include <bitset> #include <bitset>
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"

View File

@@ -9,6 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMM_H_
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/internal/emm.h" #include "media_cas_packager_sdk/internal/emm.h"
#include <cstdint>
#include <string> #include <string>
#include "testing/gmock.h" #include "testing/gmock.h"
@@ -215,7 +216,8 @@ class EmmTest : public ::testing::Test {
EXPECT_EQ(signature, expected_signature); EXPECT_EQ(signature, expected_signature);
} }
int GetPayloadLength(const std::string& serialized_emm) { int GetPayloadLength(const std::string& serialized_emm) {
return static_cast<uint16_t>(serialized_emm[kPayloadLengthStartIndex]) << 8 | return static_cast<uint16_t>(serialized_emm[kPayloadLengthStartIndex])
<< 8 |
static_cast<uint8_t>(serialized_emm[kPayloadLengthStartIndex + 1]); static_cast<uint8_t>(serialized_emm[kPayloadLengthStartIndex + 1]);
} }

View File

@@ -10,6 +10,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <cstdint>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>

View File

@@ -10,6 +10,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_EMMG_H_
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -12,6 +12,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <cstdint>
#include <memory> #include <memory>
#include "testing/gunit.h" #include "testing/gunit.h"

View File

@@ -11,6 +11,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstdint> #include <cstdint>

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/internal/simulcrypt_util.h" #include "media_cas_packager_sdk/internal/simulcrypt_util.h"
#include <cstdint>
#include <cstring> #include <cstring>
#include "glog/logging.h" #include "glog/logging.h"
@@ -74,8 +75,8 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
*message_length += param_length; *message_length += param_length;
} }
void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, void AddParam(uint16_t param_type, const uint8_t* param_value,
char* message, size_t* message_length) { uint16_t param_length, char* message, size_t* message_length) {
DCHECK(param_value); DCHECK(param_value);
DCHECK(message); DCHECK(message);
DCHECK(message_length); DCHECK(message_length);

View File

@@ -12,6 +12,7 @@
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_SIMULCRYPT_UTIL_H_
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstdint> #include <cstdint>
@@ -44,8 +45,8 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message,
size_t* message_length); size_t* message_length);
// Add a param that is |param_length| bytes long. // Add a param that is |param_length| bytes long.
void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, void AddParam(uint16_t param_type, const uint8_t* param_value,
char* message, size_t* message_length); uint16_t param_length, char* message, size_t* message_length);
} // namespace simulcrypt_util } // namespace simulcrypt_util
} // namespace cas } // namespace cas

View File

@@ -9,6 +9,7 @@
#include "media_cas_packager_sdk/internal/ts_packet.h" #include "media_cas_packager_sdk/internal/ts_packet.h"
#include <bitset> #include <bitset>
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"

View File

@@ -33,6 +33,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#include <cstdint>
#include <string> #include <string>
#include <cstdint> #include <cstdint>
@@ -78,7 +79,9 @@ class TsPacket {
uint32_t transport_scrambling_control() const { uint32_t transport_scrambling_control() const {
return transport_scrambling_control_; 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_; } uint32_t continuity_counter() const { return continuity_counter_; }
const std::string& payload() const { return payload_; } const std::string& payload() const { return payload_; }
@@ -94,7 +97,9 @@ class TsPacket {
void set_transport_scrambling_control(uint32_t v) { void set_transport_scrambling_control(uint32_t v) {
transport_scrambling_control_ = 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_continuity_counter(uint32_t v) { continuity_counter_ = v; }
void set_payload(const std::string& p) { payload_ = p; } void set_payload(const std::string& p) { payload_ = p; }

View File

@@ -12,6 +12,7 @@
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstring> #include <cstring>
#include "glog/logging.h" #include "glog/logging.h"

View File

@@ -11,6 +11,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <cstdint>
#include <string> #include <string>
#include <cstdint> #include <cstdint>

View File

@@ -10,6 +10,8 @@
#include <string.h> #include <string.h>
#include <cstdint>
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h"

View File

@@ -34,6 +34,8 @@ cc_binary(
deps = [ deps = [
":wv_cas_ca_descriptor", ":wv_cas_ca_descriptor",
":wv_cas_ecm", ":wv_cas_ecm",
":wv_cas_ecmg_client_handler",
":wv_cas_emm",
":wv_cas_key_fetcher", ":wv_cas_key_fetcher",
":wv_cas_types", ":wv_cas_types",
], ],

View File

@@ -9,6 +9,7 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include <bitset> #include <bitset>
#include <cstdint>
#include <vector> #include <vector>
#include "glog/logging.h" #include "glog/logging.h"

View File

@@ -11,6 +11,7 @@
#include <stddef.h> #include <stddef.h>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -60,7 +61,8 @@ class WvCasCaDescriptor {
// ECM stream). The descriptor will be 6 bytes plus any bytes added as // ECM stream). The descriptor will be 6 bytes plus any bytes added as
// (user-defined) private data. // (user-defined) private data.
virtual Status GenerateCaDescriptor( 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<std::string>& entitlement_key_ids, const std::vector<std::string>& entitlement_key_ids,
std::string* serialized_ca_desc) const; std::string* serialized_ca_desc) const;

View File

@@ -8,6 +8,8 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h" #include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include <cstdint>
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
#include "protos/public/media_cas.pb.h" #include "protos/public/media_cas.pb.h"

View File

@@ -8,6 +8,8 @@
#include "media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h" #include "media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h"
#include <cstdint>
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "curl/curl.h" #include "curl/curl.h"

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/public/wv_cas_ecm.h" #include "media_cas_packager_sdk/public/wv_cas_ecm.h"
#include <cstdint>
#include <memory> #include <memory>
#include "glog/logging.h" #include "glog/logging.h"
@@ -69,6 +70,7 @@ void WvCasEcm::SetServiceBlocking(
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key, Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key, const WvCasContentKeyInfo& odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { std::string* serialized_ecm) const {
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key); EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_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, return ecm_->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
serialized_ecm); group_ids, serialized_ecm);
} }
Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const { std::string* serialized_ecm) const {
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key); EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
// Make content key to 16 bytes if crypto mode is Csa2. // 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; 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, Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,

View File

@@ -9,6 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -98,6 +99,8 @@ class WvCasEcm {
// |even_key| information for even key to be encoded into ECM. // |even_key| information for even key to be encoded into ECM.
// |odd_key| information for odd 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. // |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. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |even_key| and |odd_key| contents (specifically IV sizes) must be // The |even_key| and |odd_key| contents (specifically IV sizes) must be
// consistent with the initialized settings. // consistent with the initialized settings.
@@ -106,6 +109,7 @@ class WvCasEcm {
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key, virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key, const WvCasContentKeyInfo& odd_key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const; std::string* serialized_ecm) const;
// Constructs a Widevine ECM using the provided key info. This call is // Constructs a Widevine ECM using the provided key info. This call is
@@ -113,11 +117,14 @@ class WvCasEcm {
// Args: // Args:
// |key| information for key to be encoded into ECM. // |key| information for key to be encoded into ECM.
// |track_type| the track that the key is being used to encrypt. // |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. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent // The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings. // with the initialized settings.
virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type, const std::string& track_type,
const std::vector<std::string>& group_ids,
std::string* serialized_ecm) const; std::string* serialized_ecm) const;
// Generate a TS packet with the given |ecm| as payload. // Generate a TS packet with the given |ecm| as payload.

View File

@@ -143,7 +143,7 @@ TEST_P(WvCasEcmTest, GenerateSingleKeyEcmKeyRotationEnabledError) {
EXPECT_EQ(error::INVALID_ARGUMENT, EXPECT_EQ(error::INVALID_ARGUMENT,
wv_cas_ecm wv_cas_ecm
.GenerateSingleKeyEcm(content_keys[0], kDefaultTrackTypeSd, .GenerateSingleKeyEcm(content_keys[0], kDefaultTrackTypeSd,
&actual_ecm) /*group_ids=*/{}, &actual_ecm)
.error_code()); .error_code());
} }
@@ -160,7 +160,7 @@ TEST_P(WvCasEcmTest, GenerateEcmKeyRotationDisabledError) {
EXPECT_EQ(error::INVALID_ARGUMENT, EXPECT_EQ(error::INVALID_ARGUMENT,
wv_cas_ecm wv_cas_ecm
.GenerateEcm(content_keys[0], content_keys[1], .GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, &actual_ecm) kDefaultTrackTypeSd, /*group_ids=*/{}, &actual_ecm)
.error_code()); .error_code());
} }
@@ -177,10 +177,11 @@ TEST_P(WvCasEcmTest, GenerateEcmSuccess) {
std::string actual_ecm; std::string actual_ecm;
if (key_rotation_) { if (key_rotation_) {
EXPECT_OK(wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], EXPECT_OK(wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1],
kDefaultTrackTypeSd, &actual_ecm)); kDefaultTrackTypeSd, /*group_ids=*/{},
&actual_ecm));
} else { } else {
EXPECT_OK(wv_cas_ecm.GenerateSingleKeyEcm( 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"); EXPECT_EQ(absl::BytesToHexString(actual_ecm).substr(0, 7), "4ad4020");
if (content_iv_size_ == 8) { if (content_iv_size_ == 8) {

View File

@@ -11,6 +11,7 @@
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
namespace widevine { namespace widevine {
@@ -55,12 +56,11 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
const char* const request_buffer, const char* const request_buffer,
size_t response_buffer_size, size_t response_buffer_size,
char* response_buffer, char* response_buffer,
size_t* response_length) { size_t& response_length,
if (request_buffer == nullptr || response_buffer == nullptr || size_t& processed_request_length) {
response_length == nullptr) { if (request_buffer == nullptr || response_buffer == nullptr) {
std::string error_message = std::string error_message =
"request_buffer, response_buffer and response_length must " "request_buffer and response_buffer must not be null pointer.";
"not be null pointer.";
LOG(ERROR) << error_message; LOG(ERROR) << error_message;
return {error::INVALID_ARGUMENT, 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 ", absl::StrCat("request_buffer_size is too small. Minimum required is ",
kSimulcryptMessageHeaderSizeBytes, " bytes."); kSimulcryptMessageHeaderSizeBytes, " bytes.");
LOG(ERROR) << error_message; LOG(ERROR) << error_message;
*response_length = 0; response_length = 0;
return {error::INVALID_ARGUMENT, error_message}; return {error::INVALID_ARGUMENT, error_message};
} }
if (response_buffer_size < kMinimumResponseBufferSizeBytes) { 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 ", absl::StrCat("response_buffer_size is too small. Minimum required is ",
kMinimumResponseBufferSizeBytes, " bytes."); kMinimumResponseBufferSizeBytes, " bytes.");
LOG(ERROR) << error_message; LOG(ERROR) << error_message;
*response_length = 0; response_length = 0;
return {error::INVALID_ARGUMENT, error_message}; return {error::INVALID_ARGUMENT, error_message};
} }
inner_handler_->HandleRequest(request_buffer, response_buffer, processed_request_length = inner_handler_->HandleRequest(
response_length); request_buffer, response_buffer, &response_length);
if (processed_request_length == 0) {
return {error::INTERNAL, "request not processed."};
}
return OkStatus(); return OkStatus();
} }

View File

@@ -66,10 +66,13 @@ class WvCasEcmgClientHandler {
// enough to hold the max possible response message (at least 2048 bytes). // enough to hold the max possible response message (at least 2048 bytes).
// - |response_buffer| is the buffer that holds the response message. // - |response_buffer| is the buffer that holds the response message.
// - |response_length| is the actual length of 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, Status HandleRequest(size_t request_buffer_size,
const char* const request_buffer, const char* const request_buffer,
size_t response_buffer_size, char* response_buffer, size_t response_buffer_size, char* response_buffer,
size_t* response_length); size_t& response_length,
size_t& processed_request_length);
protected: protected:
// For unit test only. // For unit test only.

View File

@@ -24,7 +24,7 @@ constexpr size_t kBufferSize = 2048;
class MockEcmgClientHandler : public EcmgClientHandler { class MockEcmgClientHandler : public EcmgClientHandler {
public: public:
MockEcmgClientHandler() : EcmgClientHandler(&config_) {} MockEcmgClientHandler() : EcmgClientHandler(&config_) {}
MOCK_METHOD(void, HandleRequest, MOCK_METHOD(size_t, HandleRequest,
(const char* const request, char* response, (const char* const request, char* response,
size_t* response_length), size_t* response_length),
(override)); (override));
@@ -47,15 +47,17 @@ TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) {
size_t response_buffer_size = kBufferSize; size_t response_buffer_size = kBufferSize;
char response_buffer[kBufferSize]; char response_buffer[kBufferSize];
size_t response_length = 0; size_t response_length = 0;
size_t processed_length = 0;
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>(); auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
EXPECT_CALL(*mock_internal_handler, 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<TestWvCasEcmgClientHandler>( auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
std::move(mock_internal_handler)); std::move(mock_internal_handler));
EXPECT_OK(handler->HandleRequest(request_buffer_size, request_buffer, EXPECT_OK(handler->HandleRequest(request_buffer_size, request_buffer,
response_buffer_size, response_buffer, response_buffer_size, response_buffer,
&response_length)); response_length, processed_length));
} }
class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test { class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test {
@@ -65,6 +67,7 @@ class WvCasEcmgClientHandlerInvalidInputTest : public ::testing::Test {
size_t response_buffer_size_ = kBufferSize; size_t response_buffer_size_ = kBufferSize;
char response_buffer_[kBufferSize]; char response_buffer_[kBufferSize];
size_t response_length_ = 0; size_t response_length_ = 0;
size_t processed_length_ = 0;
}; };
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) { TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) {
@@ -76,7 +79,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferTooSmallFail) {
EXPECT_EQ(handler EXPECT_EQ(handler
->HandleRequest(/*request_buffer_size=*/1, request_buffer_, ->HandleRequest(/*request_buffer_size=*/1, request_buffer_,
response_buffer_size_, response_buffer_, response_buffer_size_, response_buffer_,
&response_length_) response_length_, processed_length_)
.error_code(), .error_code(),
error::INVALID_ARGUMENT); error::INVALID_ARGUMENT);
EXPECT_EQ(response_length_, 0); EXPECT_EQ(response_length_, 0);
@@ -91,7 +94,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferTooSmallFail) {
EXPECT_EQ(handler EXPECT_EQ(handler
->HandleRequest(request_buffer_size_, request_buffer_, ->HandleRequest(request_buffer_size_, request_buffer_,
/*response_buffer_size=*/1, response_buffer_, /*response_buffer_size=*/1, response_buffer_,
&response_length_) response_length_, processed_length_)
.error_code(), .error_code(),
error::INVALID_ARGUMENT); error::INVALID_ARGUMENT);
EXPECT_EQ(response_length_, 0); EXPECT_EQ(response_length_, 0);
@@ -107,7 +110,7 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, RequestBufferNullFail) {
handler handler
->HandleRequest(request_buffer_size_, ->HandleRequest(request_buffer_size_,
/*request_buffer=*/nullptr, response_buffer_size_, /*request_buffer=*/nullptr, response_buffer_size_,
response_buffer_, &response_length_) response_buffer_, response_length_, processed_length_)
.error_code(), .error_code(),
error::INVALID_ARGUMENT); error::INVALID_ARGUMENT);
EXPECT_EQ(response_length_, 0); EXPECT_EQ(response_length_, 0);
@@ -122,24 +125,27 @@ TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseBufferNullFail) {
EXPECT_EQ(handler EXPECT_EQ(handler
->HandleRequest(request_buffer_size_, request_buffer_, ->HandleRequest(request_buffer_size_, request_buffer_,
response_buffer_size_, response_buffer_size_,
/*response_buffer=*/nullptr, &response_length_) /*response_buffer=*/nullptr, response_length_,
processed_length_)
.error_code(), .error_code(),
error::INVALID_ARGUMENT); error::INVALID_ARGUMENT);
EXPECT_EQ(response_length_, 0); EXPECT_EQ(response_length_, 0);
} }
TEST_F(WvCasEcmgClientHandlerInvalidInputTest, ResponseLengthNullFail) { TEST_F(WvCasEcmgClientHandlerInvalidInputTest, HandleRequestReturnedZeroFail) {
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>(); auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
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<TestWvCasEcmgClientHandler>( auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(
std::move(mock_internal_handler)); std::move(mock_internal_handler));
EXPECT_EQ(handler EXPECT_EQ(handler
->HandleRequest(request_buffer_size_, request_buffer_, ->HandleRequest(request_buffer_size_, request_buffer_,
response_buffer_size_, response_buffer_, response_buffer_size_, response_buffer_,
/*response_length=*/nullptr) response_length_, processed_length_)
.error_code(), .error_code(),
error::INVALID_ARGUMENT); error::INTERNAL);
} }
} // namespace } // namespace

View File

@@ -8,6 +8,8 @@
#include "media_cas_packager_sdk/public/wv_cas_emm.h" #include "media_cas_packager_sdk/public/wv_cas_emm.h"
#include <cstdint>
#include "glog/logging.h" #include "glog/logging.h"
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/types/span.h" #include "absl/types/span.h"

View File

@@ -9,6 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_EMM_H_
#include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/public/wv_cas_emm.h" #include "media_cas_packager_sdk/public/wv_cas_emm.h"
#include <cstdint>
#include <memory> #include <memory>
#include <vector> #include <vector>

View File

@@ -9,8 +9,10 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_ #ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
#include <cstdint>
#include <functional> #include <functional>
#include <string> #include <string>
#include <tuple>
#include <vector> #include <vector>
#include "common/status.h" #include "common/status.h"
@@ -52,18 +54,34 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 };
// Information needed for the injected entitlement keys. Used for Ecm // Information needed for the injected entitlement keys. Used for Ecm
// initialization. // 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 { 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; 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; bool is_even_key;
std::string key_id; // must be 16 bytes. // Key ID of the entitlement key, must be 16 bytes.
std::string key_value; // must be 32 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 }; 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 // |stream_id| is the stream id received at ECMG from SCS when setting up
// the stream. // the stream.
// |access_criteria| the received access criteria from SCS. // |access_criteria| the received access criteria from SCS.
// Returns EcmgCustomParameters. Negative or empty fields will be ignored. // |ecmg_params| is the output parameters by processing |access_criteria|.
typedef std::function<EcmgCustomParameters(uint16_t channel_id, uint16_t stream_id, // Negative or empty fields will be ignored.
const std::string& access_criteria)> // Returns a Status. If status is not OK, error INVALID_MESSAGE with error info
// from status will be sent to SCS.
typedef std::function<Status(uint16_t channel_id, uint16_t stream_id,
const std::string& access_criteria,
EcmgCustomParameters& ecmg_params)>
CustomAccessCriteriaProcessFunc; CustomAccessCriteriaProcessFunc;
struct EcmFingerprintingParams { struct EcmFingerprintingParams {

View File

@@ -9,12 +9,14 @@
// Widevine MediaCAS ECMG server. // Widevine MediaCAS ECMG server.
#include <netinet/in.h> #include <netinet/in.h>
#include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include <cerrno> #include <cerrno>
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
@@ -58,7 +60,7 @@ ABSL_FLAG(
"cryptography key."); "cryptography key.");
#define LISTEN_QUEUE_SIZE (20) #define LISTEN_QUEUE_SIZE (20)
#define BUFFER_SIZE (1024) #define BUFFER_SIZE (2048)
using widevine::cas::EcmgClientHandler; using widevine::cas::EcmgClientHandler;
using widevine::cas::EcmgConfig; using widevine::cas::EcmgConfig;
@@ -104,7 +106,6 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) {
char response[BUFFER_SIZE]; char response[BUFFER_SIZE];
while (true) { while (true) {
bzero(request, BUFFER_SIZE); bzero(request, BUFFER_SIZE);
bzero(response, BUFFER_SIZE);
size_t response_length = 0; size_t response_length = 0;
size_t request_length = recv(socket_fd, request, BUFFER_SIZE, 0); size_t request_length = recv(socket_fd, request, BUFFER_SIZE, 0);
if (request_length == 0) { if (request_length == 0) {
@@ -116,11 +117,21 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) {
return; return;
} }
PrintMessage("Request", request, request_length); PrintMessage("Request", request, request_length);
ecmg->HandleRequest(request, response, &response_length); size_t processed_total_length = 0;
PrintMessage("Response", response, response_length); while (processed_total_length < request_length) {
if (send(socket_fd, response, response_length, 0) < 0) { bzero(response, BUFFER_SIZE);
LOG(INFO) << "Failed to send response to client"; size_t processed_length = ecmg->HandleRequest(
return; &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; break;
} }
// Ignoring SIGCHLD signal to prevent Zombie processes.
signal(SIGCHLD, SIG_IGN);
// While loop to serve different client connections. // While loop to serve different client connections.
while (true) { while (true) {
struct sockaddr_in client_address; struct sockaddr_in client_address;
@@ -179,7 +193,7 @@ int main(int argc, char** argv) {
listen_socket_fd, reinterpret_cast<struct sockaddr*>(&client_address), listen_socket_fd, reinterpret_cast<struct sockaddr*>(&client_address),
&client_address_size); &client_address_size);
LOG(INFO) << "\nTCP connection " << client_socket_fd << " start\n"; 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"; LOG(ERROR) << "Failed to accept connection request from client";
} else { } else {
// TODO(user): Consider limit the number of forked child processes. // TODO(user): Consider limit the number of forked child processes.
@@ -200,8 +214,5 @@ int main(int argc, char** argv) {
usleep(1000); usleep(1000);
} }
// Close listening socket.
close(listen_socket_fd);
return 0; return 0;
} }

View File

@@ -13,6 +13,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include <cstdint>
#include <cstring> #include <cstring>
#include <string> #include <string>

View File

@@ -17,4 +17,5 @@ enum HashAlgorithmProto {
HASH_ALGORITHM_UNSPECIFIED = 0; HASH_ALGORITHM_UNSPECIFIED = 0;
HASH_ALGORITHM_SHA_1 = 1; HASH_ALGORITHM_SHA_1 = 1;
HASH_ALGORITHM_SHA_256 = 2; HASH_ALGORITHM_SHA_256 = 2;
HASH_ALGORITHM_SHA_384 = 3;
} }

View File

@@ -22,6 +22,9 @@ message CaDescriptorPrivateData {
// Entitlement key IDs for current content per track. Each track will allow up // Entitlement key IDs for current content per track. Each track will allow up
// to 2 entitlement key ids (odd and even entitlement keys). // to 2 entitlement key ids (odd and even entitlement keys).
repeated bytes entitlement_key_ids = 3; repeated bytes entitlement_key_ids = 3;
// The groups ids this channel belongs to.
repeated bytes group_ids = 4;
} }
// Widevine fingerprinting. // Widevine fingerprinting.
@@ -49,6 +52,15 @@ message ServiceBlocking {
message EmmPayload { message EmmPayload {
repeated Fingerprinting fingerprinting = 1; repeated Fingerprinting fingerprinting = 1;
repeated ServiceBlocking service_blocking = 2; 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 { message EcmMetaData {
@@ -89,6 +101,18 @@ message EcmKeyData {
optional bytes content_iv = 4; 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 { message EcmPayload {
// Required. Meta info carried by the ECM. // Required. Meta info carried by the ECM.
optional EcmMetaData meta_data = 1; optional EcmMetaData meta_data = 1;
@@ -100,6 +124,9 @@ message EcmPayload {
optional Fingerprinting fingerprinting = 4; optional Fingerprinting fingerprinting = 4;
// Optional. Widevine service blocking information. // Optional. Widevine service blocking information.
optional ServiceBlocking service_blocking = 5; 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. // The payload field for an ECM with signature.

View File

@@ -27,9 +27,14 @@ message CasEncryptionRequest {
// returned. // returned.
optional bool key_rotation = 4; optional bool key_rotation = 4;
// Optional value which can be used to indicate a group. // Optional value which can be used to indicate a group.
// If present the CasEncryptionResponse will return key based on the group // If present, the CasEncryptionResponse will return keys based on this group
// id. // id, instead of |content_id|.
optional bytes group_id = 5; 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 { message CasEncryptionResponse {

View File

@@ -10,6 +10,8 @@
#include "strings/serialize.h" #include "strings/serialize.h"
#include <cstdint>
#include "testing/gunit.h" #include "testing/gunit.h"
namespace widevine { namespace widevine {

View File

@@ -7,6 +7,8 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include "util/endian/endian.h" #include "util/endian/endian.h"
#include <cstdint>
#include "testing/gmock.h" #include "testing/gmock.h"
#include "testing/gunit.h" #include "testing/gunit.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"