Add support for Widevine ECM v3
Widevine ECM v3 is redesigned mainly based on protobuf, and supports new features including carrying fingerprinting and service blocking information. Existing clients must upgrade the Widevine CAS plugin to use the new ECM v3.
This commit is contained in:
@@ -11,8 +11,8 @@ git_repository(
|
||||
|
||||
git_repository(
|
||||
name = "abseil_repo",
|
||||
commit = "aa844899c937bde5d2b24f276b59997e5b668bde", #2019-08-08
|
||||
remote = "https://github.com/abseil/abseil-cpp.git",
|
||||
tag = "20200923.2",
|
||||
)
|
||||
|
||||
# Name com_google_protobuf instead of protobuf_repo because Bazel's proto rules
|
||||
@@ -42,7 +42,7 @@ http_archive(
|
||||
|
||||
git_repository(
|
||||
name = "boringssl_repo",
|
||||
commit = "14164f6fef47b7ebd97cdb0cea1624eabd6fe6b8", # 2018-11-26
|
||||
commit = "3ef9a6b03503ae25f9267473073fea9c39d9cdac", # 2020-10-27
|
||||
remote = "https://github.com/google/boringssl.git",
|
||||
)
|
||||
|
||||
|
||||
113
common/BUILD
113
common/BUILD
@@ -16,7 +16,7 @@ filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = [
|
||||
"certificate_type.h",
|
||||
"default_device_security_profile_list.h",
|
||||
"hash_algorithm.h",
|
||||
"security_profile_list.h",
|
||||
"status.h",
|
||||
],
|
||||
@@ -96,6 +96,11 @@ cc_library(
|
||||
deps = [
|
||||
":client_id_util",
|
||||
":device_status_list",
|
||||
":error_space",
|
||||
":hash_algorithm",
|
||||
":hash_algorithm_util",
|
||||
":rsa_key",
|
||||
":status",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
@@ -103,6 +108,8 @@ cc_library(
|
||||
"//protos/public:device_certificate_status_cc_proto",
|
||||
"//protos/public:device_common_cc_proto",
|
||||
"//protos/public:device_security_profile_data_cc_proto",
|
||||
"//protos/public:device_security_profile_list_cc_proto",
|
||||
"//protos/public:errors_cc_proto",
|
||||
"//protos/public:provisioned_device_info_cc_proto",
|
||||
"//protos/public:security_profile_cc_proto",
|
||||
],
|
||||
@@ -114,47 +121,21 @@ cc_test(
|
||||
srcs = ["security_profile_list_test.cc"],
|
||||
deps = [
|
||||
":client_id_util",
|
||||
":error_space",
|
||||
":hash_algorithm",
|
||||
":hash_algorithm_util",
|
||||
":rsa_key",
|
||||
":rsa_test_keys",
|
||||
":security_profile_list",
|
||||
":status",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//protos/public:device_common_cc_proto",
|
||||
"//protos/public:device_security_profile_data_cc_proto",
|
||||
"//protos/public:security_profile_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "default_device_security_profile_list",
|
||||
srcs = ["default_device_security_profile_list.cc"],
|
||||
hdrs = ["default_device_security_profile_list.h"],
|
||||
deps = [
|
||||
":client_id_util",
|
||||
":device_status_list",
|
||||
":security_profile_list",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//protos/public:client_identification_cc_proto",
|
||||
"//protos/public:device_certificate_status_cc_proto",
|
||||
"//protos/public:device_common_cc_proto",
|
||||
"//protos/public:provisioned_device_info_cc_proto",
|
||||
"//protos/public:security_profile_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "default_device_security_profile_list_test",
|
||||
timeout = "short",
|
||||
srcs = ["default_device_security_profile_list_test.cc"],
|
||||
deps = [
|
||||
":client_id_util",
|
||||
":default_device_security_profile_list",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//protos/public:device_common_cc_proto",
|
||||
"//protos/public:device_security_profile_list_cc_proto",
|
||||
"//protos/public:errors_cc_proto",
|
||||
"//protos/public:security_profile_cc_proto",
|
||||
],
|
||||
)
|
||||
@@ -189,8 +170,6 @@ cc_library(
|
||||
"certificate_client_cert.cc",
|
||||
"certificate_client_cert.h",
|
||||
"client_cert.cc",
|
||||
"dual_certificate_client_cert.cc",
|
||||
"dual_certificate_client_cert.h",
|
||||
"keybox_client_cert.cc",
|
||||
],
|
||||
hdrs = [
|
||||
@@ -426,7 +405,6 @@ cc_library(
|
||||
":sha_util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//external:openssl",
|
||||
],
|
||||
)
|
||||
@@ -494,7 +472,7 @@ cc_test(
|
||||
"//base",
|
||||
"//testing:gunit",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//external:openssl",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -871,6 +849,7 @@ cc_library(
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
"//external:openssl",
|
||||
"//protos/public:client_identification_cc_proto",
|
||||
"//protos/public:errors_cc_proto",
|
||||
"//protos/public:remote_attestation_cc_proto",
|
||||
@@ -944,11 +923,13 @@ cc_library(
|
||||
srcs = ["x509_cert.cc"],
|
||||
hdrs = ["x509_cert.h"],
|
||||
deps = [
|
||||
":ec_key",
|
||||
":openssl_util",
|
||||
":rsa_key",
|
||||
":status",
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
"//external:openssl",
|
||||
@@ -973,6 +954,7 @@ cc_test(
|
||||
srcs = ["x509_cert_test.cc"],
|
||||
deps = [
|
||||
":rsa_key",
|
||||
":status",
|
||||
":test_utils",
|
||||
":x509_cert",
|
||||
"//testing:gunit_main",
|
||||
@@ -1174,6 +1156,16 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "raw_ec_key_util",
|
||||
srcs = ["raw_ec_key_util.cc"],
|
||||
hdrs = ["raw_ec_key_util.h"],
|
||||
deps = [
|
||||
":x509_cert",
|
||||
"//base",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "hash_algorithm_util_test",
|
||||
srcs = ["hash_algorithm_util_test.cc"],
|
||||
@@ -1183,3 +1175,46 @@ cc_test(
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "raw_ec_key_util_test",
|
||||
srcs = ["raw_ec_key_util_test.cc"],
|
||||
deps = [
|
||||
":ec_key",
|
||||
":ec_test_keys",
|
||||
":raw_ec_key_util",
|
||||
":rsa_test_keys",
|
||||
":x509_test_certificates",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "x509_test_certificates",
|
||||
testonly = 1,
|
||||
srcs = ["x509_test_certificates.cc"],
|
||||
hdrs = ["x509_test_certificates.h"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "remote_attestation_verifier_test",
|
||||
srcs = ["remote_attestation_verifier_test.cc"],
|
||||
deps = [
|
||||
":drm_service_certificate",
|
||||
":remote_attestation_verifier",
|
||||
":status",
|
||||
":x509_test_certificates",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//protos/public:client_identification_cc_proto",
|
||||
"//protos/public:errors_cc_proto",
|
||||
"//protos/public:remote_attestation_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "openssl/aes.h"
|
||||
#include "openssl/cmac.h"
|
||||
#include "openssl/digest.h"
|
||||
#include "openssl/evp.h"
|
||||
#include "openssl/hmac.h"
|
||||
#include "openssl/sha.h"
|
||||
@@ -59,12 +60,28 @@ const uint32_t kCBCSSchemeID =
|
||||
// Creates a SHA-256 HMAC signature for the given message.
|
||||
std::string CreateSignatureHmacSha256(absl::string_view key,
|
||||
absl::string_view message) {
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
return CreateSignatureHmac(EVP_sha256(), digest, key, message);
|
||||
}
|
||||
|
||||
// Creates a SHA-384 HMAC signature for the given message.
|
||||
std::string CreateSignatureHmacSha384(absl::string_view key,
|
||||
absl::string_view message) {
|
||||
unsigned char digest[SHA384_DIGEST_LENGTH];
|
||||
return CreateSignatureHmac(EVP_sha384(), digest, key, message);
|
||||
}
|
||||
|
||||
std::string CreateSignatureHmac(const EVP_MD* hash_algorithm,
|
||||
unsigned char* digest, absl::string_view key,
|
||||
absl::string_view message) {
|
||||
if (hash_algorithm == nullptr || digest == nullptr) {
|
||||
return "";
|
||||
}
|
||||
HMAC_CTX ctx;
|
||||
HMAC_CTX_init(&ctx);
|
||||
HMAC_Init(&ctx, key.data(), key.size(), EVP_sha256());
|
||||
HMAC_Init(&ctx, key.data(), key.size(), hash_algorithm);
|
||||
HMAC_Update(&ctx, reinterpret_cast<const unsigned char*>(message.data()),
|
||||
message.size());
|
||||
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||
unsigned int digest_len;
|
||||
HMAC_Final(&ctx, digest, &digest_len);
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
@@ -79,20 +96,18 @@ bool VerifySignatureHmacSha256(absl::string_view key,
|
||||
return CreateSignatureHmacSha256(key, message) == signature;
|
||||
}
|
||||
|
||||
// Compares the SHA-384 HMAC against the provided signature.
|
||||
bool VerifySignatureHmacSha384(absl::string_view key,
|
||||
absl::string_view signature,
|
||||
absl::string_view message) {
|
||||
return CreateSignatureHmacSha384(key, message) == signature;
|
||||
}
|
||||
|
||||
// Creates a SHA-1 HMAC signature for the given message.
|
||||
std::string CreateSignatureHmacSha1(absl::string_view key,
|
||||
absl::string_view message) {
|
||||
HMAC_CTX ctx;
|
||||
HMAC_CTX_init(&ctx);
|
||||
HMAC_Init(&ctx, key.data(), key.size(), EVP_sha1());
|
||||
HMAC_Update(&ctx, reinterpret_cast<const unsigned char*>(message.data()),
|
||||
message.size());
|
||||
unsigned char digest[SHA_DIGEST_LENGTH];
|
||||
unsigned int digest_len;
|
||||
HMAC_Final(&ctx, digest, &digest_len);
|
||||
HMAC_CTX_cleanup(&ctx);
|
||||
std::string s(reinterpret_cast<char*>(digest), digest_len);
|
||||
return s;
|
||||
return CreateSignatureHmac(EVP_sha1(), digest, key, message);
|
||||
}
|
||||
|
||||
// Compares the SHA-1 HMAC against the provided signature.
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "openssl/digest.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace crypto_util {
|
||||
@@ -64,12 +66,27 @@ std::string DeriveSigningKey(absl::string_view key, absl::string_view context,
|
||||
std::string CreateSignatureHmacSha256(absl::string_view key,
|
||||
absl::string_view message);
|
||||
|
||||
// Helper function to create a SHA-384 HMAC signature for the given message.
|
||||
std::string CreateSignatureHmacSha384(absl::string_view key,
|
||||
absl::string_view message);
|
||||
// Helper function to create a HMAC signature for the specified hash algorithm
|
||||
// and message.
|
||||
std::string CreateSignatureHmac(const EVP_MD* hash_algorithm,
|
||||
unsigned char* digest, absl::string_view key,
|
||||
absl::string_view message);
|
||||
|
||||
// Helper function which compares the SHA-256 HMAC against the provided
|
||||
// signature.
|
||||
bool VerifySignatureHmacSha256(absl::string_view key,
|
||||
absl::string_view signature,
|
||||
absl::string_view message);
|
||||
|
||||
// Helper function which compares the SHA-384 HMAC against the provided
|
||||
// signature.
|
||||
bool VerifySignatureHmacSha384(absl::string_view key,
|
||||
absl::string_view signature,
|
||||
absl::string_view message);
|
||||
|
||||
// Helper function to create a SHA-1 HMAC signature for the given message.
|
||||
std::string CreateSignatureHmacSha1(absl::string_view key,
|
||||
absl::string_view message);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Description:
|
||||
// Container of Widevine default security profiless.
|
||||
|
||||
#ifndef COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||
#define COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||
|
||||
#include "common/security_profile_list.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class DefaultDeviceSecurityProfileList : public SecurityProfileList {
|
||||
public:
|
||||
DefaultDeviceSecurityProfileList();
|
||||
~DefaultDeviceSecurityProfileList() override {}
|
||||
|
||||
// Initialize the security profile list. The list is initially empty, this
|
||||
// function will populate the list with default profiles. The size of the
|
||||
// list is returned.
|
||||
int Init() override;
|
||||
|
||||
private:
|
||||
// Initialize the list with Widevine default profiles. The size of the
|
||||
// profile list after the additions is returned.
|
||||
virtual int AddDefaultProfiles();
|
||||
virtual int GetDefaultProfileStrings(
|
||||
std::vector<std::string>* default_profile_strings) const;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include "common/ec_key.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "openssl/base.h"
|
||||
@@ -203,6 +205,34 @@ bool ECPrivateKey::SerializedKey(std::string* serialized_key) const {
|
||||
return ec_util::SerializeECPrivateKey(key_.get(), serialized_key);
|
||||
}
|
||||
|
||||
bool ECPrivateKey::GetRawPrivateKey(std::string* raw_private_key) const {
|
||||
if (raw_private_key == nullptr) {
|
||||
LOG(WARNING) << "|raw_private_key| cannot be nullptr.";
|
||||
return false;
|
||||
}
|
||||
const EC_GROUP* group = EC_KEY_get0_group(key());
|
||||
if (group == nullptr) {
|
||||
LOG(WARNING) << "Failed to load EC key group.";
|
||||
return false;
|
||||
}
|
||||
int key_size_bits = EC_GROUP_get_degree(group);
|
||||
if (key_size_bits == 0) {
|
||||
LOG(WARNING) << "Key size is 0.";
|
||||
return false;
|
||||
}
|
||||
const BIGNUM* key_bn = EC_KEY_get0_private_key(key());
|
||||
int key_size_bytes = (key_size_bits + 7) / 8;
|
||||
std::vector<uint8_t> tmp_raw_private_key_data(key_size_bytes);
|
||||
int num_bytes = BN_bn2bin(key_bn, tmp_raw_private_key_data.data());
|
||||
if (num_bytes != key_size_bytes) {
|
||||
LOG(WARNING) << "Failed to convert EC private key BIGNUM to binary data.";
|
||||
return false;
|
||||
}
|
||||
raw_private_key->assign(tmp_raw_private_key_data.begin(),
|
||||
tmp_raw_private_key_data.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
ECPublicKey::ECPublicKey(EC_KEY* ec_key) : key_(ec_key) {
|
||||
CHECK(key() != nullptr);
|
||||
CHECK(EC_KEY_get0_private_key(key()) == nullptr);
|
||||
@@ -310,4 +340,24 @@ bool ECPublicKey::GetPointEncodedKey(std::string* encoded_key) const {
|
||||
return ec_util::GetPublicKeyPoint(key_.get(), encoded_key);
|
||||
}
|
||||
|
||||
bool ECPublicKey::GetRawPublicKey(std::string* raw_public_key) const {
|
||||
if (raw_public_key == nullptr) {
|
||||
LOG(WARNING) << "|raw_public_key| cannot be nullptr.";
|
||||
return false;
|
||||
}
|
||||
std::string tmp_raw_public_key;
|
||||
if (!ec_util::GetPublicKeyPoint(key(), &tmp_raw_public_key) ||
|
||||
tmp_raw_public_key.empty()) {
|
||||
LOG(WARNING) << "Failed to encoded EC_KEY";
|
||||
}
|
||||
CHECK_EQ(tmp_raw_public_key[0], POINT_CONVERSION_UNCOMPRESSED);
|
||||
// Public key is X9.62 uncompressed format. Expected structure is <tag><X><Y>,
|
||||
// where tag = 0x04 indicating uncompressed, and X and Y lengths are equal but
|
||||
// dependent on ECC curve. Tag is removed because Intel Sigma 2.1.0 library
|
||||
// requires only X and Y bytes.
|
||||
raw_public_key->assign(tmp_raw_public_key.begin() + 1,
|
||||
tmp_raw_public_key.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
@@ -83,6 +83,11 @@ class ECPrivateKey {
|
||||
|
||||
virtual bool SerializedKey(std::string* serialized_key) const;
|
||||
|
||||
// Gets raw private key bytes.
|
||||
// |raw_private_key| is where the raw bytes are stored.
|
||||
// Returns true on success and false on error.
|
||||
virtual bool GetRawPrivateKey(std::string* raw_private_key) const;
|
||||
|
||||
private:
|
||||
friend class ECPublicKey;
|
||||
|
||||
@@ -135,6 +140,11 @@ class ECPublicKey {
|
||||
// octets for the X and Y coordinates.
|
||||
virtual bool GetPointEncodedKey(std::string* encoded_key) const;
|
||||
|
||||
// Gets raw public key bytes.
|
||||
// |raw_public_key| is where the raw bytes are stored.
|
||||
// Returns true on success and false on error.
|
||||
virtual bool GetRawPublicKey(std::string* raw_public_key) const;
|
||||
|
||||
private:
|
||||
friend class ECPrivateKey;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "openssl/bn.h"
|
||||
#include "openssl/digest.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/evp.h"
|
||||
#include "openssl/rsa.h"
|
||||
@@ -67,6 +68,32 @@ std::string GetMessageDigest(const std::string& message,
|
||||
return "";
|
||||
}
|
||||
|
||||
const EVP_MD* GetHashMd(widevine::HashAlgorithm hash_algorithm) {
|
||||
switch (hash_algorithm) {
|
||||
case widevine::HashAlgorithm::kUnspecified:
|
||||
case widevine::HashAlgorithm::kSha1:
|
||||
return EVP_sha1();
|
||||
case widevine::HashAlgorithm::kSha256:
|
||||
return EVP_sha256();
|
||||
}
|
||||
LOG(FATAL) << "Unexpected hash algorithm: "
|
||||
<< static_cast<int>(hash_algorithm);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsMessageTooSmall(const std::string& message) {
|
||||
DCHECK(!message.empty());
|
||||
// The most significant byte is encoded first in the message. See
|
||||
// https://tools.ietf.org/html/rfc8017. To make sure the big number is greater
|
||||
// than 2^64, we need to make sure there is at least a non-zero number in the
|
||||
// first "LENGTH_IN_BITS - 64" bits of the message,
|
||||
// i.e. "LENGTH_IN_BYTES - 8" bytes of the message.
|
||||
const int kMinimumSizeInBytes = 8;
|
||||
for (int i = 0; i < message.length() - kMinimumSizeInBytes; i++) {
|
||||
if (message[i] != 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace widevine {
|
||||
@@ -117,7 +144,6 @@ bool RsaPrivateKey::Decrypt(const std::string& encrypted_message,
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RsaPrivateKey::GenerateSignature(const std::string& message,
|
||||
HashAlgorithm hash_algorithm,
|
||||
std::string* signature) const {
|
||||
@@ -134,12 +160,18 @@ bool RsaPrivateKey::GenerateSignature(const std::string& message,
|
||||
return false;
|
||||
}
|
||||
|
||||
const EVP_MD* hash = GetHashMd(hash_algorithm);
|
||||
if (hash == nullptr) {
|
||||
LOG(ERROR) << "No hash md";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add PSS padding.
|
||||
size_t rsa_size = RSA_size(key_);
|
||||
std::string padded_digest(rsa_size, 0);
|
||||
if (!RSA_padding_add_PKCS1_PSS_mgf1(
|
||||
key_, reinterpret_cast<unsigned char*>(&padded_digest[0]),
|
||||
reinterpret_cast<unsigned char*>(&message_digest[0]), EVP_sha1(),
|
||||
reinterpret_cast<unsigned char*>(&message_digest[0]), hash,
|
||||
EVP_sha1(), kPssSaltLength)) {
|
||||
LOG(ERROR) << "RSA padding failure: "
|
||||
<< OpenSSLErrorString(ERR_get_error());
|
||||
@@ -222,6 +254,8 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
}
|
||||
size_t rsa_size = RSA_size(key_);
|
||||
encrypted_message->assign(rsa_size, 0);
|
||||
const int kRetryAttempt = 1;
|
||||
for (int i = 0; i < 1 + kRetryAttempt; i++) {
|
||||
if (RSA_public_encrypt(
|
||||
clear_message.size(),
|
||||
const_cast<unsigned char*>(
|
||||
@@ -232,7 +266,10 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
<< OpenSSLErrorString(ERR_get_error());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (!IsMessageTooSmall(*encrypted_message)) return true;
|
||||
}
|
||||
LOG(ERROR) << "RSA public encryption randomness error";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
@@ -261,18 +298,23 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hash the message using SHA1 using corresponding hash algorithm.
|
||||
// Hash the message using the corresponding hash algorithm.
|
||||
std::string message_digest = GetMessageDigest(message, hash_algorithm);
|
||||
if (message_digest.empty()) {
|
||||
LOG(ERROR) << "Empty message digest";
|
||||
return false;
|
||||
}
|
||||
|
||||
const EVP_MD* hash = GetHashMd(hash_algorithm);
|
||||
if (hash == nullptr) {
|
||||
LOG(ERROR) << "No hash md";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify PSS padding.
|
||||
if (RSA_verify_PKCS1_PSS_mgf1(
|
||||
key_, reinterpret_cast<unsigned char*>(&message_digest[0]),
|
||||
EVP_sha1(), EVP_sha1(),
|
||||
reinterpret_cast<unsigned char*>(&padded_digest[0]),
|
||||
key_, reinterpret_cast<unsigned char*>(&message_digest[0]), hash,
|
||||
EVP_sha1(), reinterpret_cast<unsigned char*>(&padded_digest[0]),
|
||||
kPssSaltLength) == 0) {
|
||||
LOG(ERROR) << "RSA Verify PSS padding failure: "
|
||||
<< OpenSSLErrorString(ERR_get_error());
|
||||
|
||||
@@ -15,25 +15,32 @@
|
||||
#define COMMON_SECURITY_PROFILE_LIST_H_
|
||||
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "common/hash_algorithm.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/device_security_profile_data.pb.h"
|
||||
#include "protos/public/device_security_profile_list.pb.h"
|
||||
#include "protos/public/provisioned_device_info.pb.h"
|
||||
#include "protos/public/security_profile.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
using ClientCapabilities = ClientIdentification::ClientCapabilities;
|
||||
|
||||
const char kDefaultProfileOwnerName[] = "Widevine";
|
||||
|
||||
// The SecurityProfileList will hold all security profiles. During license
|
||||
// acquisition, information from the client and information from the server are
|
||||
// combined to deternmine the device's security profile level.
|
||||
|
||||
// TODO(user): Clean up the virtual/protected functions once subclass
|
||||
// default_device_security_profile_list gets removed.
|
||||
class SecurityProfileList {
|
||||
public:
|
||||
explicit SecurityProfileList(const std::string& profile_namespace);
|
||||
virtual ~SecurityProfileList() {}
|
||||
|
||||
// Initialize the security profile list. The size of the profile list is
|
||||
// returned.
|
||||
// Initialize the security profile list with Widevine default profiles. The
|
||||
// size of the profile list is returned.
|
||||
virtual int Init();
|
||||
|
||||
// Add the specified profile to the existing list of profiles. Returns true
|
||||
@@ -45,7 +52,7 @@ class SecurityProfileList {
|
||||
// The number of profiles is returned.
|
||||
virtual int GetQualifiedProfilesFromSpecifiedProfiles(
|
||||
const std::vector<std::string>& profiles_to_check,
|
||||
const ClientIdentification& client_id,
|
||||
const std::string& owner, const ClientIdentification& client_id,
|
||||
const ProvisionedDeviceInfo& device_info,
|
||||
std::vector<std::string>* qualified_profiles) const;
|
||||
|
||||
@@ -53,14 +60,28 @@ class SecurityProfileList {
|
||||
// requirements for the this device. The number of profiles is returned.
|
||||
virtual int GetQualifiedProfiles(
|
||||
const ClientIdentification& client_id,
|
||||
const ProvisionedDeviceInfo& device_info,
|
||||
const ProvisionedDeviceInfo& device_info, const std::string& owner,
|
||||
std::vector<std::string>* qualified_profiles) const;
|
||||
|
||||
// Return true if a profile exist matching the specified |name|.
|
||||
// |security_profile| is owned by the caller and is populated if a profile
|
||||
// exist.
|
||||
bool GetProfileByName(const std::string& name,
|
||||
SecurityProfile* security_profile) const;
|
||||
// Return true if a profile exist matching the specified parameters {|name|,
|
||||
// |owner|}. |security_profiles| is owned by the caller and is populated if
|
||||
// one or more profile exist. For default DSP, the output profiles should
|
||||
// contain single record. For custom DSP, it may contain multiple records
|
||||
// since active dsp and inactive dsp could share the same dsp_name under the
|
||||
// same owner.
|
||||
bool GetProfileByNameAndOwner(
|
||||
const std::string& name, const std::string& owner,
|
||||
std::vector<SecurityProfile>* security_profiles) const;
|
||||
|
||||
// Populates |security_profiles| owned by the content owner.
|
||||
int GetProfilesByOwner(const std::string& owner,
|
||||
std::vector<SecurityProfile>* security_profiles) const;
|
||||
|
||||
// Populates |owner_list| for security profiles. |is_default_dsp| boolean
|
||||
// indicates the owner_list for default dsp or custom dsp.
|
||||
int GetProfilesOwnerList(const bool is_default_dsp,
|
||||
std::vector<std::string>* owner_list) const;
|
||||
|
||||
// Return the device security capabilities. |drm_info| is populated with
|
||||
// data from |client_id| and |device_info|. |drm_info| must not be null and
|
||||
// is owned by the caller.
|
||||
@@ -74,10 +95,33 @@ class SecurityProfileList {
|
||||
// Return a list of profile names.
|
||||
virtual void GetProfileNames(std::vector<std::string>* profile_names) const;
|
||||
|
||||
// Deserialized SignedDeviceSecurityProfiles for custom DSPs.
|
||||
static Status DeserializeSignedDeviceSecurityProfiles(
|
||||
const std::string& serialized_signed_device_security_profiles,
|
||||
std::string* serialized_device_security_profiles,
|
||||
HashAlgorithm* hash_algorithm, std::string* signature);
|
||||
|
||||
// Validate signature and update security profile list for custom dsps.
|
||||
Status ValidateAndUpdateProfileList(
|
||||
const std::string& root_certificate_public_key,
|
||||
const std::string& serialized_device_security_profiles,
|
||||
HashAlgorithm hash_algorithm, const std::string& signature,
|
||||
int* added_profile_num);
|
||||
|
||||
protected:
|
||||
void ClearAllProfiles();
|
||||
|
||||
private:
|
||||
// Add Widevine default profiles into profile_list. The number of added
|
||||
// default profiles will be returned.
|
||||
virtual int AddDefaultProfiles();
|
||||
// Add Widevine custom profiles into profile_list. The number of added custom
|
||||
// profiles will be returned.
|
||||
virtual int AddCustomProfiles(
|
||||
const DeviceSecurityProfileList& device_security_profile_list);
|
||||
virtual int GetDefaultProfileStrings(
|
||||
std::vector<std::string>* default_profile_strings) const;
|
||||
|
||||
bool DoesProfileQualify(const SecurityProfile& profile,
|
||||
const ClientIdentification& client_id,
|
||||
const ProvisionedDeviceInfo& device_info) const;
|
||||
@@ -87,9 +131,19 @@ class SecurityProfileList {
|
||||
bool IsProfileActive(const SecurityProfile& profile,
|
||||
int64_t current_time_seconds) const;
|
||||
|
||||
bool InsertProfileLocked(const SecurityProfile& profile_to_insert)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Return true if a profile already exists in the profile_list.
|
||||
bool DoesProfileExistLocked(const SecurityProfile& profile) const
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void ClearAllDefaultProfilesLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
void ClearAllCustomProfilesLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
mutable absl::Mutex mutex_;
|
||||
// Security profiles
|
||||
std::string profile_namespace_;
|
||||
// TODO(user): Modify as Map<owner, DSPs>.
|
||||
std::vector<SecurityProfile> security_profiles_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ constexpr char kTestEcmgChannelStatus[] = {
|
||||
constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x01', '\x01', // message_type - Stream_setup
|
||||
'\x00', '\xae', // message_length
|
||||
'\x00', '\xa8', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
@@ -117,9 +117,6 @@ constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = {
|
||||
'\x00', '\x10', // parameter_type - nominal_CP_duration
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x64', // parameter_value
|
||||
'\x80', '\x02', // parameter_type - STREAM_TRACK_TYPE
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x03', // parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
|
||||
@@ -245,7 +242,7 @@ constexpr char kTestEcmgCwProvision[] = {
|
||||
constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
|
||||
'\x03', // protocol_version
|
||||
'\x02', '\x01', // message_type - CW_provision
|
||||
'\x00', '\xee', // message_length
|
||||
'\x00', '\xe8', // message_length
|
||||
'\x00', '\x0e', // parameter_type - ECM_channel_id
|
||||
'\x00', '\x02', // parameter_length
|
||||
'\x00', '\x01', // parameter_value
|
||||
@@ -269,16 +266,13 @@ constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
|
||||
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', //
|
||||
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', //
|
||||
'\x00', '\x0d', // parameter_type - access_criteria
|
||||
'\x00', '\xa6', // parameter_length
|
||||
'\x00', '\xa0', // parameter_length
|
||||
'\x80', '\x00', // access_criteria parameter_type - AGE_RESTRICTION
|
||||
'\x00', '\x01', // parameter_length
|
||||
'\x00', // parameter_value
|
||||
'\x80', '\x01', // access_criteria parameter_type - CRYPTO_MODE
|
||||
'\x00', '\x07', // parameter_length
|
||||
'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value
|
||||
'\x80', '\x02', // access_criteria parameter_type - STREAM_TRACK_TYPE
|
||||
'\x00', '\x02', // parameter_length
|
||||
'S', 'D', // parameter_value
|
||||
'\x80', '\x03', // access_criteria parameter_type - CONTENT_IV
|
||||
'\x00', '\x10', // parameter_length
|
||||
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', //
|
||||
|
||||
@@ -21,37 +21,60 @@
|
||||
#include "media_cas_packager_sdk/public/wv_cas_ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
const size_t kContentIvSize = 16; // 8 or 16
|
||||
const bool kKeyRotation = true; // whether key rotation is enabled
|
||||
const char kCryptoMode[] =
|
||||
namespace {
|
||||
|
||||
constexpr int kEcmVersion = 3; // Choices are 2 and 3.
|
||||
|
||||
constexpr size_t kContentIvSize = 16; // 8 or 16
|
||||
constexpr bool kKeyRotation = true; // whether key rotation is enabled
|
||||
constexpr char kCryptoMode[] =
|
||||
"AesScte"; // "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte"
|
||||
const int kEcmPid = 149; // PID for the ECM packet
|
||||
const int kAgeRestriction = 0; // Age restriction for the ECM
|
||||
const char kOutputFile[] =
|
||||
constexpr int kEcmPid = 149; // PID for the ECM packet
|
||||
constexpr int kAgeRestriction = 0; // Age restriction for the ECM
|
||||
constexpr char kOutputFile[] =
|
||||
"/tmp/ecm.ts"; // ECM TS packet will be output to here
|
||||
const uint8_t kTableId = 0x80; // 0x80 or 0x81
|
||||
const char kDefaultTrackTypeSd[] = "SD";
|
||||
constexpr uint8_t kTableId = 0x80; // 0x80 or 0x81
|
||||
constexpr char kDefaultTrackTypeSd[] = "SD";
|
||||
|
||||
const char kEvenKey[] = "even_key........"; // 16 bytes
|
||||
const char kEvenKeyId[] = "even_key_id....."; // 16 bytes
|
||||
const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
|
||||
const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
|
||||
const char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes
|
||||
const char kEvenEntitlementKey[] =
|
||||
constexpr char kEvenKey[] = "even_key........"; // 16 bytes
|
||||
constexpr char kEvenKeyId[] = "even_key_id....."; // 16 bytes
|
||||
constexpr char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
|
||||
constexpr char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes
|
||||
constexpr char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes
|
||||
constexpr char kEvenEntitlementKey[] =
|
||||
"fakefakefakefakefakefakefake1..."; // 32 bytes
|
||||
const char kEvenWrapIv[] = "even_warp_iv...."; // 16 bytes
|
||||
constexpr char kEvenWrapIv[] = "even_warp_iv...."; // 16 bytes
|
||||
|
||||
const char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
const char kOddKeyId[] = "odd_key_id......"; // 16 bytes
|
||||
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
|
||||
const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
|
||||
const char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes
|
||||
const char kOddEntitlementKey[] =
|
||||
constexpr char kOddKey[] = "odd_key........."; // 16 bytes
|
||||
constexpr char kOddKeyId[] = "odd_key_id......"; // 16 bytes
|
||||
constexpr char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
|
||||
constexpr char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes
|
||||
constexpr char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes
|
||||
constexpr char kOddEntitlementKey[] =
|
||||
"fakefakefakefakefakefakefake2..."; // 32 bytes
|
||||
const char kOddWrapIv[] = "odd_warp_iv....."; // 16 bytes
|
||||
constexpr char kOddWrapIv[] = "odd_warp_iv....."; // 16 bytes
|
||||
|
||||
// Fingerprinting and service blocking are only available with ECM v3+.
|
||||
constexpr char kFingerprintingControl[] = "ctr";
|
||||
constexpr char kServiceBlockingDeviceGroup1[] = "group";
|
||||
constexpr char kServiceBlockingDeviceGroup2[] = "g2";
|
||||
constexpr unsigned char kTestECPrivateKey2Secp256r1[] = {
|
||||
0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x34, 0x9a, 0xf2, 0x95,
|
||||
0x94, 0xd4, 0xca, 0xb9, 0xa0, 0x81, 0xe4, 0x1c, 0xf5, 0xde, 0x8d,
|
||||
0x23, 0xf6, 0x79, 0xba, 0x3c, 0x6e, 0xc9, 0x0b, 0x56, 0x0f, 0x07,
|
||||
0x5e, 0x9f, 0xe9, 0x38, 0x18, 0xfc, 0xa0, 0x0a, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42,
|
||||
0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b,
|
||||
0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32,
|
||||
0xa1, 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3,
|
||||
0x20, 0x12, 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77,
|
||||
0x53, 0x77, 0x8f, 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11,
|
||||
0xfa, 0x72, 0x6a, 0xbe, 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86};
|
||||
|
||||
const size_t kTsPacketSize = 188;
|
||||
const size_t kMaxPossibleTsPacketsSizeBytes = 6 * kTsPacketSize;
|
||||
|
||||
using widevine::cas::EcmVersion;
|
||||
using widevine::cas::EntitlementKeyInfo;
|
||||
using widevine::cas::WvCasContentKeyInfo;
|
||||
using widevine::cas::WvCasEcmParameters;
|
||||
@@ -68,6 +91,9 @@ WvCasEcmParameters CreateWvCasEcmParameters(bool key_rotation,
|
||||
std::cerr << "Unsupported crypto mode " << kCryptoMode << std::endl;
|
||||
}
|
||||
params.age_restriction = kAgeRestriction;
|
||||
params.ecm_version = kEcmVersion <= 2 ? EcmVersion::kV2 : EcmVersion::kV3;
|
||||
params.ecc_private_signing_key.assign(std::begin(kTestECPrivateKey2Secp256r1),
|
||||
std::end(kTestECPrivateKey2Secp256r1));
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -115,6 +141,8 @@ std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
|
||||
return content_keys;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
WvCasEcmParameters params =
|
||||
CreateWvCasEcmParameters(kKeyRotation, kContentIvSize);
|
||||
@@ -122,6 +150,17 @@ int main(int argc, char** argv) {
|
||||
CreateInjectedEntitlements(kKeyRotation);
|
||||
widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements);
|
||||
|
||||
if (params.ecm_version == EcmVersion::kV3) {
|
||||
widevine::cas::EcmFingerprintingParams fingerprinting_params;
|
||||
fingerprinting_params.control = kFingerprintingControl;
|
||||
wv_cas_ecm.SetFingerprinting(&fingerprinting_params);
|
||||
|
||||
widevine::cas::EcmServiceBlockingParams service_blocking_params;
|
||||
service_blocking_params.device_groups = {kServiceBlockingDeviceGroup1,
|
||||
kServiceBlockingDeviceGroup2};
|
||||
wv_cas_ecm.SetServiceBlocking(&service_blocking_params);
|
||||
}
|
||||
|
||||
std::vector<WvCasContentKeyInfo> content_keys =
|
||||
CreateContentKeyInfo(kKeyRotation, kContentIvSize);
|
||||
std::string ecm;
|
||||
@@ -147,16 +186,17 @@ int main(int argc, char** argv) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
// Generate ECM TS Packet.
|
||||
uint8_t packet[kTsPacketSize];
|
||||
uint8_t packet[kMaxPossibleTsPacketsSizeBytes];
|
||||
ssize_t packet_size = kMaxPossibleTsPacketsSizeBytes;
|
||||
uint8_t continuity_counter; // not used.
|
||||
status = wv_cas_ecm.GenerateTsPacket(ecm, kEcmPid, kTableId,
|
||||
&continuity_counter, packet);
|
||||
status = wv_cas_ecm.GenerateTsPacket(
|
||||
ecm, kEcmPid, kTableId, &continuity_counter, packet, &packet_size);
|
||||
if (!status.ok()) {
|
||||
std::cerr << "Failed to create ECM TS packet: " << status << std::endl;
|
||||
return -1;
|
||||
} else {
|
||||
std::cout << "TS packet bytes: ";
|
||||
for (size_t i = 0; i < kTsPacketSize; i++) {
|
||||
for (size_t i = 0; i < packet_size; i++) {
|
||||
printf("'\\x%02x', ", static_cast<uint16_t>(packet[i]));
|
||||
}
|
||||
std::cout << std::endl;
|
||||
@@ -165,7 +205,7 @@ int main(int argc, char** argv) {
|
||||
std::ofstream file;
|
||||
file.open(kOutputFile, std::ios_base::binary);
|
||||
assert(file.is_open());
|
||||
file.write(reinterpret_cast<char*>(packet), kTsPacketSize);
|
||||
file.write(reinterpret_cast<char*>(packet), packet_size);
|
||||
file.close();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -34,6 +34,18 @@ using widevine::cas::WvCasEcmgClientHandler;
|
||||
constexpr int kServerPortNumber = 1234;
|
||||
constexpr int kListenQueueSize = 20;
|
||||
constexpr int kBufferSizeBytes = 2048;
|
||||
constexpr unsigned char kTestECPrivateKey2Secp256r1[] = {
|
||||
0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x34, 0x9a, 0xf2, 0x95,
|
||||
0x94, 0xd4, 0xca, 0xb9, 0xa0, 0x81, 0xe4, 0x1c, 0xf5, 0xde, 0x8d,
|
||||
0x23, 0xf6, 0x79, 0xba, 0x3c, 0x6e, 0xc9, 0x0b, 0x56, 0x0f, 0x07,
|
||||
0x5e, 0x9f, 0xe9, 0x38, 0x18, 0xfc, 0xa0, 0x0a, 0x06, 0x08, 0x2a,
|
||||
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42,
|
||||
0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b,
|
||||
0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32,
|
||||
0xa1, 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3,
|
||||
0x20, 0x12, 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77,
|
||||
0x53, 0x77, 0x8f, 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11,
|
||||
0xfa, 0x72, 0x6a, 0xbe, 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86};
|
||||
|
||||
void BuildEcmgConfig(EcmgConfig* config) {
|
||||
config->delay_start = 200; // in milliseconds.
|
||||
@@ -43,6 +55,9 @@ void BuildEcmgConfig(EcmgConfig* config) {
|
||||
config->access_criteria_transfer_mode = 0;
|
||||
config->number_of_content_keys = 2;
|
||||
config->crypto_mode = widevine::cas::CryptoMode::kAesCtr;
|
||||
config->ecc_private_signing_key.assign(
|
||||
std::begin(kTestECPrivateKey2Secp256r1),
|
||||
std::end(kTestECPrivateKey2Secp256r1));
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
|
||||
@@ -25,9 +25,14 @@ cc_library(
|
||||
srcs = ["ecm.cc"],
|
||||
hdrs = ["ecm.h"],
|
||||
deps = [
|
||||
":ecm_serializer_cc",
|
||||
":util",
|
||||
"//base",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
@@ -41,8 +46,12 @@ cc_test(
|
||||
srcs = ["ecm_test.cc"],
|
||||
deps = [
|
||||
":ecm",
|
||||
":ecm_serializer_cc",
|
||||
":mpeg2ts",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -92,6 +101,7 @@ cc_library(
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:crypto_util",
|
||||
"//common:random_util",
|
||||
"//common:status",
|
||||
@@ -104,6 +114,7 @@ cc_test(
|
||||
size = "small",
|
||||
srcs = ["ecmg_client_handler_test.cc"],
|
||||
deps = [
|
||||
":ecm",
|
||||
":ecmg_client_handler",
|
||||
":simulcrypt_util",
|
||||
":util",
|
||||
@@ -231,6 +242,7 @@ cc_library(
|
||||
":ts_packet",
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
@@ -243,9 +255,7 @@ cc_test(
|
||||
],
|
||||
deps = [
|
||||
":util",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -258,6 +268,7 @@ cc_library(
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/time",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:ec_key",
|
||||
"//common:hash_algorithm",
|
||||
"//common:status",
|
||||
@@ -266,6 +277,64 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_serializer_v2",
|
||||
srcs = ["ecm_serializer_v2.cc"],
|
||||
hdrs = ["ecm_serializer_v2.h"],
|
||||
deps = [
|
||||
":ecm_serializer",
|
||||
"//base",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
"//common:string_util",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_serializer",
|
||||
hdrs = ["ecm_serializer.h"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_serializer_v3",
|
||||
srcs = ["ecm_serializer_v3.cc"],
|
||||
hdrs = ["ecm_serializer_v3.h"],
|
||||
deps = [
|
||||
":ecm_serializer",
|
||||
"//base",
|
||||
"@abseil_repo//absl/status",
|
||||
"@abseil_repo//absl/status:statusor",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:ec_key",
|
||||
"//common:hash_algorithm",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_serializer_cc",
|
||||
srcs = ["ecm_serializer.cc"],
|
||||
hdrs = ["ecm_serializer.h"],
|
||||
deps = [
|
||||
":ecm_serializer_v2",
|
||||
":ecm_serializer_v3",
|
||||
"//base",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "emm_test",
|
||||
srcs = ["emm_test.cc"],
|
||||
@@ -282,3 +351,39 @@ cc_test(
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_serializer_v2_test",
|
||||
srcs = ["ecm_serializer_v2_test.cc"],
|
||||
deps = [
|
||||
":ecm_serializer_v2",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_serializer_v3_test",
|
||||
srcs = ["ecm_serializer_v3_test.cc"],
|
||||
deps = [
|
||||
":ecm_serializer_v3",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/types:optional",
|
||||
"//common:ec_key",
|
||||
"//common:ec_test_keys",
|
||||
"//common:hash_algorithm",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/public:wv_cas_types",
|
||||
"//protos/public:media_cas_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_serializer_test",
|
||||
srcs = ["ecm_serializer_test.cc"],
|
||||
deps = [
|
||||
":ecm_serializer_cc",
|
||||
"//testing:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <bitset>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -24,81 +25,16 @@ namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
// ECM constants
|
||||
static constexpr size_t kMaxEcmSizeBytes = 184;
|
||||
constexpr size_t kKeyIdSizeBytes = 16;
|
||||
constexpr size_t kKeyDataSizeBytes = 16;
|
||||
constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
constexpr size_t kWrappingKeySizeBytes = 32; // entitlement key
|
||||
|
||||
// Bitfield lengths for the mp2ts ecm payload.
|
||||
// This ECM structure is specific to the Widevine CAS implementation.
|
||||
|
||||
// Byte 0, 1
|
||||
static constexpr int kNumBitsCaSystemIdField = 16;
|
||||
|
||||
// Byte 2
|
||||
static constexpr int kNumBitsEcmVersionField = 8;
|
||||
|
||||
// Byte 3
|
||||
// Unused bits (mbz, must be zero)
|
||||
static constexpr int kNumBitsUnusedFieldByte3 = 3;
|
||||
// Values for Decrypt Mode are from enum CryptoMode
|
||||
static constexpr int kNumBitsDecryptModeField = 4;
|
||||
static constexpr int kNumBitsRotationEnabledField = 1;
|
||||
|
||||
// Byte 4
|
||||
// Size of IVs.
|
||||
// Values for IV size fields are from enum EcmIvSize
|
||||
// Note: Wrapped key IV size is always 16. The field is encoded, but it must
|
||||
// always be set to 1.
|
||||
static constexpr int kNumBitsWrappedKeyIvSizeField = 1;
|
||||
static constexpr int kNumBitsContentIvSizeField = 1;
|
||||
static constexpr int kNumBitsAgeRestriction = 5;
|
||||
// Unused bits (mbz, must be zero)
|
||||
static constexpr int kNumBitsUnusedFieldByte4 = 1;
|
||||
|
||||
// Total bits equals number of bytes * 8.
|
||||
static constexpr int kBitSetTotalSize = 40;
|
||||
|
||||
// Remaining bytes (starting from the 6th byte) hold entitled key info.
|
||||
static constexpr size_t kKeyIdSizeBytes = 16;
|
||||
static constexpr size_t kKeyDataSizeBytes = 16;
|
||||
static constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
static constexpr size_t kWrappingKeySizeBytes = 32; // entitlement key
|
||||
static constexpr size_t kWrappingKeyIvSizeBytes = 16;
|
||||
|
||||
// BitField constants for the ECM payload
|
||||
|
||||
// CA System ID for Widevine. From table in
|
||||
// https://en.wikipedia.org/wiki/Conditional_access
|
||||
// This should be the only file found.
|
||||
static constexpr int kWvCasCaSystemId = 0x4AD4;
|
||||
|
||||
// Version - this should be incremented if there are non-backwards compatible
|
||||
// changes to the ECM.
|
||||
static constexpr int kEcmVersion = 2;
|
||||
|
||||
// Settings for RotationEnabled field.
|
||||
static constexpr int kRotationDisabled = 0;
|
||||
static constexpr int kRotationEnabled = 1;
|
||||
|
||||
// Convert from boolean for rotation to rotation field value.
|
||||
int RotationFieldValue(bool rotation_required) {
|
||||
return rotation_required ? kRotationEnabled : kRotationDisabled;
|
||||
}
|
||||
|
||||
// Setting for Unused field.
|
||||
static constexpr int kUnusedZero = 0;
|
||||
|
||||
// Convert from IV size to IV size field value.
|
||||
int IvSizeFieldValue(size_t iv_size) {
|
||||
return (8 == iv_size) ? kIvSize8 : kIvSize16;
|
||||
}
|
||||
|
||||
void SerializeKeyInfo(const EntitledKeyInfo& key_info, std::string* ecm_buf) {
|
||||
absl::StrAppend(ecm_buf, key_info.entitlement_key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_value);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_iv);
|
||||
absl::StrAppend(ecm_buf, key_info.content_iv);
|
||||
}
|
||||
// CA System ID for Widevine.
|
||||
constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
// New CA System ID range for Widevine, all inclusive.
|
||||
constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
|
||||
|
||||
bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
|
||||
if (param == kIvSize8) {
|
||||
@@ -131,6 +67,14 @@ Status Ecm::Initialize(
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
age_restriction_ = ecm_init_parameters.age_restriction;
|
||||
ecc_private_signing_key_ = ecm_init_parameters.ecc_private_signing_key;
|
||||
|
||||
cas_id_ = ecm_init_parameters.cas_id;
|
||||
if (cas_id_ != kWidevineSystemId &&
|
||||
(cas_id_ < kWidevineNewSystemIdLowerBound ||
|
||||
cas_id_ > kWidevineNewSystemIdUpperBound)) {
|
||||
return {error::INVALID_ARGUMENT, "Invalid CA system ID."};
|
||||
}
|
||||
|
||||
ClearEntitlementKeys();
|
||||
for (const auto& entitlement : injected_entitlements) {
|
||||
@@ -143,91 +87,123 @@ Status Ecm::Initialize(
|
||||
"Improper injected entitlement keys.");
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.ecm_version == EcmVersion::kV2) {
|
||||
ecm_serializer_ = EcmSerializer::Create(EcmSerializerVersion::kV2);
|
||||
} else {
|
||||
ecm_serializer_ = EcmSerializer::Create(EcmSerializerVersion::kV3);
|
||||
}
|
||||
|
||||
// Everything is set up including entitlement keys.
|
||||
initialized_ = true;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void Ecm::SetFingerprinting(const EcmFingerprintingParams* fingerprinting) {
|
||||
absl::WriterMutexLock lock(&ecm_params_mutex_);
|
||||
fingerprinting_ = fingerprinting == nullptr
|
||||
? absl::nullopt
|
||||
: absl::make_optional(*fingerprinting);
|
||||
}
|
||||
|
||||
void Ecm::SetServiceBlocking(const EcmServiceBlockingParams* service_blocking) {
|
||||
absl::WriterMutexLock lock(&ecm_params_mutex_);
|
||||
service_blocking_ = service_blocking == nullptr
|
||||
? absl::nullopt
|
||||
: absl::make_optional(*service_blocking);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
absl::ReaderMutexLock lock(&ecm_params_mutex_);
|
||||
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (!paired_keys_required_) {
|
||||
if (even_key == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "even_key must not be null."};
|
||||
}
|
||||
if (!paired_keys_required_ && odd_key != nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Key rotation not enabled - use GenerateSingleKeyEcm()."};
|
||||
"odd_key should be null as key rotation is disabled."};
|
||||
}
|
||||
if (paired_keys_required_ && odd_key == nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"odd_key can not be null as key rotation is enabled."};
|
||||
}
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(even_key);
|
||||
keys.push_back(odd_key);
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm);
|
||||
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;
|
||||
serializer_params.crypto_mode = crypto_mode_;
|
||||
serializer_params.age_restriction = age_restriction_;
|
||||
serializer_params.cas_id = cas_id_;
|
||||
serializer_params.even_key = {even_key->entitlement_key_id, even_key->key_id,
|
||||
even_key->wrapped_key_value,
|
||||
even_key->wrapped_key_iv, even_key->content_iv};
|
||||
if (odd_key != nullptr) {
|
||||
serializer_params.odd_key = {odd_key->entitlement_key_id, odd_key->key_id,
|
||||
odd_key->wrapped_key_value,
|
||||
odd_key->wrapped_key_iv, odd_key->content_iv};
|
||||
}
|
||||
|
||||
serializer_params.fingerprinting = fingerprinting_;
|
||||
serializer_params.service_blocking = service_blocking_;
|
||||
serializer_params.ecc_private_signing_key = ecc_private_signing_key_;
|
||||
|
||||
status = ecm_serializer_->SerializeEcm(serializer_params, serialized_ecm);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
if (!initialized_) {
|
||||
return {error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (paired_keys_required_) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Key rotation enabled - use GenerateEcm()."};
|
||||
}
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(key);
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
|
||||
Status status = ValidateKeys(keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = WrapEntitledKeys(track_type, keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = ValidateWrappedKeys(keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// TODO(user): validate inputs, compare against current values
|
||||
// TODO(user): replace current values.
|
||||
|
||||
// Generate TS packet payload for ECM, pass back to caller.
|
||||
serialized_ecm->assign(SerializeEcm(keys));
|
||||
return OkStatus();
|
||||
return GenerateEcm(key, nullptr, track_type, serialized_ecm);
|
||||
}
|
||||
|
||||
Status Ecm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id,
|
||||
uint8_t* continuity_counter, uint8_t* packet) {
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet, &bytes_modified);
|
||||
if (!status.ok() || bytes_modified != kTsPacketSize) {
|
||||
memset(packet, 0, kTsPacketSize);
|
||||
return {error::INTERNAL, "Failed to generate TS packet"};
|
||||
uint8_t* continuity_counter,
|
||||
absl::Span<uint8_t> packet,
|
||||
ssize_t* bytes_modified) {
|
||||
if (ecm.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "ecm must not be empty"};
|
||||
}
|
||||
if (continuity_counter == nullptr || bytes_modified == nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"continuity_counter and bytes_modified must not be null"};
|
||||
}
|
||||
|
||||
size_t buf_size_needed = CalculateTsBufferSizeForEcmEmm(ecm.size());
|
||||
if (packet.size() < buf_size_needed) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Packet buffer is too small. Minimum required: ",
|
||||
buf_size_needed, " bytes.")};
|
||||
}
|
||||
|
||||
Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
packet.subspan(0, buf_size_needed));
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
*bytes_modified += buf_size_needed;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
@@ -274,7 +250,7 @@ Status Ecm::WrapKey(const std::string& wrapping_key,
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(wrapping_iv, kWrappingKeyIvSizeBytes);
|
||||
status = ValidateIv(wrapping_iv, kWrappedKeyIvSizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -291,54 +267,6 @@ Status Ecm::WrapKey(const std::string& wrapping_key,
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
for (const auto& key : keys) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateKeyValue(key->key_value, kKeyDataSizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->content_iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->wrapped_key_iv, kWrappedKeyIvSizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
for (const auto& key : keys) {
|
||||
Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateKeyValue(key->wrapped_key_value, kKeyDataSizeBytes);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Wrapped key is bad.";
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->wrapped_key_iv, kWrappedKeyIvSizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->content_iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status Ecm::ValidateKeyId(const std::string& key_id) const {
|
||||
if (key_id.size() != kKeyIdSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT, "Key ID must be 16 bytes."};
|
||||
@@ -363,47 +291,6 @@ Status Ecm::ValidateIv(const std::string& iv, size_t size) const {
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
std::string Ecm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) const {
|
||||
// Five bytes (40 bits including padding)
|
||||
std::bitset<kNumBitsCaSystemIdField> ca_system_id(kWvCasCaSystemId);
|
||||
std::bitset<kNumBitsEcmVersionField> ecm_version(kEcmVersion);
|
||||
std::bitset<kNumBitsUnusedFieldByte3> unused(kUnusedZero);
|
||||
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
|
||||
static_cast<int>(crypto_mode()));
|
||||
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
|
||||
RotationFieldValue(paired_keys_required()));
|
||||
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(
|
||||
IvSizeFieldValue(kWrappedKeyIvSizeBytes));
|
||||
std::bitset<kNumBitsContentIvSizeField> content_iv_size(
|
||||
IvSizeFieldValue(this->content_iv_size()));
|
||||
std::bitset<kNumBitsAgeRestriction> age_restriction(
|
||||
static_cast<int>(this->age_restriction()));
|
||||
std::bitset<kNumBitsUnusedFieldByte4> padding(kUnusedZero);
|
||||
|
||||
// Converts bitset to string.
|
||||
std::string ecm_bitset = absl::StrCat(
|
||||
ca_system_id.to_string(), ecm_version.to_string(), unused.to_string(),
|
||||
decrypt_mode.to_string(), rotation_enabled.to_string(),
|
||||
wrapped_key_iv_size.to_string(), content_iv_size.to_string(),
|
||||
age_restriction.to_string(), padding.to_string());
|
||||
if (ecm_bitset.size() != kBitSetTotalSize) {
|
||||
LOG(FATAL) << "ECM bitset incorrect size: " << ecm_bitset.size();
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
Status status =
|
||||
string_util::BitsetStringToBinaryString(ecm_bitset, &serialized_ecm);
|
||||
if (!status.ok() || serialized_ecm.empty()) {
|
||||
LOG(FATAL) << "Failed to convert ECM bitset to std::string";
|
||||
}
|
||||
|
||||
// Appends entitled key info.
|
||||
for (const auto& key : keys) {
|
||||
SerializeKeyInfo(*key, &serialized_ecm);
|
||||
}
|
||||
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
size_t Ecm::CountEntitlementKeys() const {
|
||||
size_t count = 0;
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
@@ -414,9 +301,9 @@ size_t Ecm::CountEntitlementKeys() const {
|
||||
|
||||
bool Ecm::CheckEntitlementKeys() const {
|
||||
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 "
|
||||
<< track.first << ": " << (paired_keys_required() ? 2 : 1)
|
||||
<< track.first << ": " << (paired_keys_required_ ? 2 : 1)
|
||||
<< " expected, " << track.second.size() << " received.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,17 @@
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -39,22 +44,22 @@ struct EntitledKeyInfo {
|
||||
};
|
||||
|
||||
// Information needed to start a new ECM stream.
|
||||
// Fields:
|
||||
// |content_iv_size| size of all content key IVs in the ECM stream.
|
||||
// A constant of type EcmIvSize specifying 8 or 16.
|
||||
// |key_rotation_enabled| the encryption uses multiple keys in rotation.
|
||||
// |crypto_mode| the encryption mode used for the content stream.
|
||||
// A constant of type CryptoMode.
|
||||
// |track_types| a vector of track ID (std::string) that specify the set of track
|
||||
// types of interest. |track_types| is deprecated and is ignored by
|
||||
// Ecm::Initialize().
|
||||
// |age_restriction| minimum age required; the value represents actual age.
|
||||
struct EcmInitParameters {
|
||||
// Size of all content key IVs in the ECM stream.
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
// The encryption uses multiple keys in rotation or not.
|
||||
bool key_rotation_enabled = true;
|
||||
// The encryption mode used for the content stream.
|
||||
CryptoMode crypto_mode = CryptoMode::kAesCtr;
|
||||
std::vector<std::string> track_types; // deprecated.
|
||||
// minimum age required; the value represents actual age.
|
||||
uint8_t age_restriction = 0;
|
||||
// CA system id that is in the ECM. Must be 0x4AD4 or 0x56C0~0x56C9 (all
|
||||
// inclusive).
|
||||
uint16_t cas_id = 0x4AD4;
|
||||
EcmVersion ecm_version = EcmVersion::kV2;
|
||||
// Private signing key used to sign ECM data. Must be an elliptic-curve
|
||||
// cryptography key.
|
||||
std::string ecc_private_signing_key;
|
||||
};
|
||||
|
||||
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
|
||||
@@ -86,8 +91,22 @@ class Ecm {
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements);
|
||||
|
||||
// Set fingerprinting info that will be embedded into the generated ECM. The
|
||||
// configuration will be used in all following ECMs generated by calling
|
||||
// GenerateEcm() or GenerateSingleKeyEcm().
|
||||
// |fingerprinting| may be set to nullptr to clear the fingerprinting info.
|
||||
virtual void SetFingerprinting(const EcmFingerprintingParams* fingerprinting);
|
||||
|
||||
// Set service blocking info that will be embedded into the generated ECM. The
|
||||
// configuration will be used in all following ECMs generated by calling
|
||||
// GenerateEcm() or GenerateSingleKeyEcm().
|
||||
// |service_blocking| may be set to nullptr to clear the service blocking
|
||||
// info.
|
||||
virtual void SetServiceBlocking(
|
||||
const EcmServiceBlockingParams* service_blocking);
|
||||
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
// Stream packet payload.
|
||||
// Args:
|
||||
// |even_key| information for even key to be encoded into ECM.
|
||||
// |odd_key| information for odd key to be encoded into ECM.
|
||||
@@ -103,8 +122,8 @@ class Ecm {
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes). This call is specifically for the case
|
||||
// where key rotation is disabled.
|
||||
// Stream packet payload. This call is specifically for the case where key
|
||||
// rotation is disabled.
|
||||
// Args:
|
||||
// |key| information for key to be encoded into ECM.
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
@@ -128,20 +147,23 @@ class Ecm {
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
// - |continuity_counter| continuity_counter for the ECM packet,
|
||||
// it will be incremented, only last 4 bits are used
|
||||
// - |packet| a buffer of size at least 188 bytes to be used to return
|
||||
// the generated TS packet
|
||||
// - |packet| is the output buffer. Must be big enough to hold an additional
|
||||
// 188 bytes * number of TS packets needed to put |ecm|.
|
||||
// (kMaxPossibleTsPacketsSizeBytes = 1128 bytes).
|
||||
// - |bytes_modified| will be incremented by the used buffer size if insertion
|
||||
// of ECM into |buffer| is successful.
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
const absl::Span<uint8_t> packet,
|
||||
ssize_t* bytes_modified);
|
||||
|
||||
protected: // For unit tests.
|
||||
// Take the input entitled |keys| and our current state, and generate
|
||||
// the ECM representing the keys.
|
||||
virtual std::string SerializeEcm(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
virtual void SetEcmSerializer(std::unique_ptr<EcmSerializer> 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.
|
||||
@@ -189,11 +211,6 @@ class Ecm {
|
||||
}
|
||||
}
|
||||
|
||||
// Common helper for GenerateEcm() and GenerateSingleKeyEcm()
|
||||
virtual Status GenerateEcmCommon(const std::vector<EntitledKeyInfo*>& keys,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|.
|
||||
// Returns the resulting wrapped key in |wrapped_key|.
|
||||
// Return a status indicating whether there has been any error.
|
||||
@@ -202,36 +219,32 @@ class Ecm {
|
||||
const std::string& key_value,
|
||||
std::string* wrapped_key) const;
|
||||
|
||||
virtual Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
virtual Status ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) const;
|
||||
|
||||
Status ValidateKeyId(const std::string& key_id) const;
|
||||
Status ValidateKeyValue(const std::string& key_value,
|
||||
size_t key_value_size) const;
|
||||
Status ValidateIv(const std::string& iv, size_t size) const;
|
||||
|
||||
virtual CryptoMode crypto_mode() const { return crypto_mode_; }
|
||||
virtual uint8_t age_restriction() const { return age_restriction_; }
|
||||
virtual bool paired_keys_required() const { return paired_keys_required_; }
|
||||
virtual size_t content_iv_size() const { return content_iv_size_; }
|
||||
|
||||
// Set to true when the object has been properly initialized.
|
||||
bool initialized_ = false;
|
||||
// Content ID for this ECM stream.
|
||||
std::string content_id_;
|
||||
// Provider ID for this ECM stream.
|
||||
std::string content_provider_;
|
||||
// Content IV size may be 8 or 16. Size is set once during Initialize().
|
||||
size_t content_iv_size_ = 8;
|
||||
// Remember if a pair of keys is required (for key rotation).
|
||||
bool paired_keys_required_ = false;
|
||||
CryptoMode crypto_mode_ = CryptoMode::kAesCtr;
|
||||
uint8_t age_restriction_ = 0;
|
||||
uint16_t cas_id_ = 0;
|
||||
std::string ecc_private_signing_key_;
|
||||
// Entitlement keys needed for ECM generation.
|
||||
// The keys are added when the CasEncryptionResponse is processed.
|
||||
// Maps from track_type to one/two EntitlementKeyIdValue with even key first.
|
||||
std::map<std::string, std::list<EntitlementKeyIdValue>> entitlement_keys_;
|
||||
std::unique_ptr<EcmSerializer> ecm_serializer_;
|
||||
|
||||
mutable absl::Mutex ecm_params_mutex_;
|
||||
absl::optional<EcmFingerprintingParams> fingerprinting_
|
||||
ABSL_GUARDED_BY(ecm_params_mutex_);
|
||||
absl::optional<EcmServiceBlockingParams> service_blocking_
|
||||
ABSL_GUARDED_BY(ecm_params_mutex_);
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
30
media_cas_packager_sdk/internal/ecm_serializer.cc
Normal file
30
media_cas_packager_sdk/internal/ecm_serializer.cc
Normal file
@@ -0,0 +1,30 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
std::unique_ptr<EcmSerializer> EcmSerializer::Create(
|
||||
EcmSerializerVersion version) {
|
||||
switch (version) {
|
||||
case EcmSerializerVersion::kV2:
|
||||
return absl::make_unique<EcmSerializerV2>();
|
||||
case EcmSerializerVersion::kV3:
|
||||
return absl::make_unique<EcmSerializerV3>();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
70
media_cas_packager_sdk/internal/ecm_serializer.h
Normal file
70
media_cas_packager_sdk/internal/ecm_serializer.h
Normal file
@@ -0,0 +1,70 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This file defines an interface class EcmSerializer that can be used to
|
||||
// generate serialized ECMs.
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/types/optional.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Content key infomartion used to generate serialized ecm.
|
||||
struct EcmSerializerKeyInfo {
|
||||
std::string entitlement_key_id;
|
||||
// content_key_id has been deprecated since v3.
|
||||
std::string content_key_id;
|
||||
std::string wrapped_key_value;
|
||||
std::string wrapped_key_iv;
|
||||
std::string content_iv;
|
||||
};
|
||||
|
||||
// Parameters passed into ECM serializer to generate ECM.
|
||||
struct EcmSerializerParams {
|
||||
uint16_t cas_id;
|
||||
CryptoMode crypto_mode;
|
||||
uint8_t age_restriction;
|
||||
EcmSerializerKeyInfo even_key;
|
||||
EcmSerializerKeyInfo odd_key;
|
||||
absl::optional<EcmFingerprintingParams> fingerprinting;
|
||||
absl::optional<EcmServiceBlockingParams> service_blocking;
|
||||
// Private signing key used to sign ECM data. Must be an elliptic-curve
|
||||
// cryptography key.
|
||||
std::string ecc_private_signing_key;
|
||||
};
|
||||
|
||||
enum class EcmSerializerVersion { kV2 = 0, kV3 };
|
||||
|
||||
// Interface for different versions of ECM serializer.
|
||||
class EcmSerializer {
|
||||
public:
|
||||
EcmSerializer() = default;
|
||||
virtual ~EcmSerializer() = default;
|
||||
static std::unique_ptr<EcmSerializer> Create(EcmSerializerVersion version);
|
||||
|
||||
// Generates a |serialize_ecm| based on input |params|.
|
||||
// Returns a status inidicating the operation is success or not.
|
||||
virtual Status SerializeEcm(const EcmSerializerParams& params,
|
||||
std::string* serialized_ecm) const = 0;
|
||||
|
||||
// Returns version of the serializer.
|
||||
virtual EcmSerializerVersion Version() const = 0;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_H_
|
||||
39
media_cas_packager_sdk/internal/ecm_serializer_test.cc
Normal file
39
media_cas_packager_sdk/internal/ecm_serializer_test.cc
Normal file
@@ -0,0 +1,39 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
class EcmSerializerCreateTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<EcmSerializerVersion> {};
|
||||
|
||||
TEST_P(EcmSerializerCreateTest, VersionExpected) {
|
||||
EcmSerializerVersion expected_version = GetParam();
|
||||
|
||||
std::unique_ptr<EcmSerializer> serializer =
|
||||
EcmSerializer::Create(expected_version);
|
||||
|
||||
ASSERT_TRUE(serializer != nullptr);
|
||||
EXPECT_EQ(serializer->Version(), expected_version);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmSerializerCreateTest, EcmSerializerCreateTest,
|
||||
testing::Values(EcmSerializerVersion::kV2,
|
||||
EcmSerializerVersion::kV3));
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
192
media_cas_packager_sdk/internal/ecm_serializer_v2.cc
Normal file
192
media_cas_packager_sdk/internal/ecm_serializer_v2.cc
Normal file
@@ -0,0 +1,192 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
#include "common/string_util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kEcmVersion = 2;
|
||||
// Bitfield lengths for the mp2ts ecm payload.
|
||||
// This ECM structure is specific to the Widevine CAS implementation.
|
||||
// Byte 0, 1
|
||||
constexpr int kNumBitsCaSystemIdField = 16;
|
||||
|
||||
// Byte 2
|
||||
constexpr int kNumBitsEcmVersionField = 8;
|
||||
|
||||
// Byte 3
|
||||
// Unused bits (mbz, must be zero)
|
||||
constexpr int kNumBitsUnusedFieldByte3 = 3;
|
||||
// Values for Decrypt Mode are from enum CryptoMode
|
||||
constexpr int kNumBitsDecryptModeField = 4;
|
||||
constexpr int kNumBitsRotationEnabledField = 1;
|
||||
|
||||
// Byte 4
|
||||
// Size of IVs.
|
||||
// Values for IV size fields are from enum EcmIvSize
|
||||
// Note: Wrapped key IV size is always 16. The field is encoded, but it must
|
||||
// always be set to 1.
|
||||
constexpr int kNumBitsWrappedKeyIvSizeField = 1;
|
||||
constexpr int kNumBitsContentIvSizeField = 1;
|
||||
constexpr int kNumBitsAgeRestriction = 5;
|
||||
// Unused bits (mbz, must be zero)
|
||||
constexpr int kNumBitsUnusedFieldByte4 = 1;
|
||||
|
||||
// Total bits equals number of bytes * 8.
|
||||
constexpr int kBitSetTotalSize = 40;
|
||||
|
||||
// Remaining bytes (starting from the 6th byte) hold entitled key info.
|
||||
constexpr size_t kKeyIdSizeBytes = 16;
|
||||
constexpr size_t kKeyDataSizeBytes = 16;
|
||||
constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
constexpr size_t kContentIvSize8Bytes = 8;
|
||||
constexpr size_t kContentIvSize16Bytes = 16;
|
||||
|
||||
// Settings for RotationEnabled field.
|
||||
constexpr int kRotationDisabled = 0;
|
||||
constexpr int kRotationEnabled = 1;
|
||||
|
||||
// Setting for Unused field.
|
||||
constexpr int kUnusedZero = 0;
|
||||
|
||||
// Convert from IV size to IV size field value.
|
||||
int IvSizeFieldValue(size_t iv_size) {
|
||||
return (iv_size == 8) ? kIvSize8 : kIvSize16;
|
||||
}
|
||||
|
||||
void SerializeKeyInfo(const EcmSerializerKeyInfo& key_info,
|
||||
std::string* ecm_buf) {
|
||||
absl::StrAppend(ecm_buf, key_info.entitlement_key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.content_key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_value);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_iv);
|
||||
absl::StrAppend(ecm_buf, key_info.content_iv);
|
||||
}
|
||||
|
||||
Status ValidateKeyInfo(const EcmSerializerKeyInfo& key_info) {
|
||||
if (key_info.entitlement_key_id.size() != kKeyIdSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid entitlement_key_id size. Expect ",
|
||||
kKeyIdSizeBytes, " but got ",
|
||||
key_info.entitlement_key_id.size())};
|
||||
}
|
||||
if (key_info.content_key_id.size() != kKeyIdSizeBytes) {
|
||||
return {
|
||||
error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid content_key_id size. Expect ", kKeyIdSizeBytes,
|
||||
" but got ", key_info.content_key_id.size())};
|
||||
}
|
||||
if (key_info.wrapped_key_value.size() != kKeyDataSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid wrapped_key_value size. Expect ",
|
||||
kKeyDataSizeBytes, " but got ",
|
||||
key_info.wrapped_key_value.size())};
|
||||
}
|
||||
if (key_info.wrapped_key_iv.size() != kWrappedKeyIvSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid wrapped_key_iv size. Expect ",
|
||||
kWrappedKeyIvSizeBytes, " but got ",
|
||||
key_info.wrapped_key_iv.size())};
|
||||
}
|
||||
if ((key_info.content_iv.size() != kContentIvSize8Bytes) &&
|
||||
(key_info.content_iv.size() != kContentIvSize16Bytes)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid content_iv size. Expect ",
|
||||
kContentIvSize8Bytes, " or ", kContentIvSize16Bytes,
|
||||
" but got ", key_info.content_iv.size())};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
bool HasNoneEmptyField(const EcmSerializerKeyInfo& key_info) {
|
||||
return !(key_info.entitlement_key_id.empty() &&
|
||||
key_info.content_key_id.empty() && key_info.wrapped_key_iv.empty() &&
|
||||
key_info.wrapped_key_value.empty() && key_info.content_iv.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EcmSerializerV2::SerializeEcm(const EcmSerializerParams& params,
|
||||
std::string* serialized_ecm) const {
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
|
||||
if (params.crypto_mode == CryptoMode::kInvalid) {
|
||||
return {error::INVALID_ARGUMENT, "Invalid crypo mode."};
|
||||
}
|
||||
Status status = ValidateKeyInfo(params.even_key);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Invalid even key info";
|
||||
return status;
|
||||
}
|
||||
bool has_odd_key = HasNoneEmptyField(params.odd_key);
|
||||
if (has_odd_key) {
|
||||
status = ValidateKeyInfo(params.odd_key);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Invalid odd key info";
|
||||
return status;
|
||||
}
|
||||
if (params.even_key.content_iv.size() != params.odd_key.content_iv.size()) {
|
||||
return {error::INVALID_ARGUMENT, "Content IV size must match."};
|
||||
}
|
||||
}
|
||||
|
||||
// Five bytes (40 bits including padding)
|
||||
std::bitset<kNumBitsCaSystemIdField> ca_system_id(params.cas_id);
|
||||
std::bitset<kNumBitsEcmVersionField> ecm_version(kEcmVersion);
|
||||
std::bitset<kNumBitsUnusedFieldByte3> unused(kUnusedZero);
|
||||
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
|
||||
static_cast<int>(params.crypto_mode));
|
||||
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
|
||||
has_odd_key ? kRotationEnabled : kRotationDisabled);
|
||||
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(
|
||||
IvSizeFieldValue(kWrappedKeyIvSizeBytes));
|
||||
std::bitset<kNumBitsContentIvSizeField> content_iv_size(
|
||||
IvSizeFieldValue(params.even_key.content_iv.size()));
|
||||
std::bitset<kNumBitsAgeRestriction> age_restriction(
|
||||
static_cast<int>(params.age_restriction));
|
||||
std::bitset<kNumBitsUnusedFieldByte4> padding(kUnusedZero);
|
||||
|
||||
// Converts bitset to string.
|
||||
std::string ecm_bitset = absl::StrCat(
|
||||
ca_system_id.to_string(), ecm_version.to_string(), unused.to_string(),
|
||||
decrypt_mode.to_string(), rotation_enabled.to_string(),
|
||||
wrapped_key_iv_size.to_string(), content_iv_size.to_string(),
|
||||
age_restriction.to_string(), padding.to_string());
|
||||
if (ecm_bitset.size() != kBitSetTotalSize) {
|
||||
LOG(ERROR) << "ECM bitset incorrect size: " << ecm_bitset.size();
|
||||
return {error::INTERNAL, "Failed to convert ECM header bitset."};
|
||||
}
|
||||
|
||||
status = string_util::BitsetStringToBinaryString(ecm_bitset, serialized_ecm);
|
||||
if (!status.ok() || serialized_ecm->empty()) {
|
||||
LOG(ERROR) << "Failed to convert ECM bitset to std::string";
|
||||
return {error::INTERNAL, "Failed to convert ECM bitset to string."};
|
||||
}
|
||||
|
||||
SerializeKeyInfo(params.even_key, serialized_ecm);
|
||||
if (has_odd_key) {
|
||||
SerializeKeyInfo(params.odd_key, serialized_ecm);
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
39
media_cas_packager_sdk/internal/ecm_serializer_v2.h
Normal file
39
media_cas_packager_sdk/internal/ecm_serializer_v2.h
Normal file
@@ -0,0 +1,39 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V2_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V2_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Generates serialized ECM version 2.
|
||||
class EcmSerializerV2 : public EcmSerializer {
|
||||
public:
|
||||
EcmSerializerV2() = default;
|
||||
EcmSerializerV2(const EcmSerializerV2&) = delete;
|
||||
EcmSerializerV2& operator=(const EcmSerializerV2&) = delete;
|
||||
~EcmSerializerV2() override = default;
|
||||
|
||||
Status SerializeEcm(const EcmSerializerParams& params,
|
||||
std::string* serialized_ecm) const override;
|
||||
|
||||
EcmSerializerVersion Version() const override {
|
||||
return EcmSerializerVersion::kV2;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V2_H_
|
||||
254
media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc
Normal file
254
media_cas_packager_sdk/internal/ecm_serializer_v2_test.cc
Normal file
@@ -0,0 +1,254 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v2.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/status.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kWvCasCaSystemId = 0x4AD4;
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2";
|
||||
constexpr char kEntitlementId[] = "entitlement_id..";
|
||||
constexpr char kEntitlementId2[] = "entitlement_id.2";
|
||||
constexpr char kContentKeyId[] = "content_key_id..";
|
||||
constexpr char kContentKeyId2[] = "content_key_id.2";
|
||||
constexpr char kContentIv8Bytes[] = "c_iv....";
|
||||
constexpr char kContentIv8Bytes2[] = "c_iv...2";
|
||||
constexpr char kContentIv16Bytes[] = "c_iv....c_iv....";
|
||||
constexpr char kContentIv16Bytes2[] = "c_iv....c_iv...2";
|
||||
constexpr char kWrappedContentKey[] = "wrapped_key.....";
|
||||
constexpr char kWrappedContentKey2[] = "wrapped_key....2";
|
||||
constexpr int kAgeRestriction = 0;
|
||||
constexpr size_t kEcmHeaderSize = 5;
|
||||
|
||||
class EcmSerializerV2Test : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
serializer_params_.cas_id = kWvCasCaSystemId;
|
||||
serializer_params_.crypto_mode = CryptoMode::kAesCtr;
|
||||
serializer_params_.age_restriction = kAgeRestriction;
|
||||
serializer_params_.even_key = {kEntitlementId, kContentKeyId,
|
||||
kWrappedContentKey, kWrappedKeyIv,
|
||||
kContentIv16Bytes};
|
||||
serializer_params_.odd_key = {kEntitlementId2, kContentKeyId2,
|
||||
kWrappedContentKey2, kWrappedKeyIv2,
|
||||
kContentIv16Bytes2};
|
||||
}
|
||||
|
||||
void ValidateEcmHeaderFields(const std::string& ecm_string,
|
||||
bool rotation_enabled, int age_restriction,
|
||||
CryptoMode crypto_mode, EcmIvSize content_iv) {
|
||||
ASSERT_GE(ecm_string.size(), kEcmHeaderSize);
|
||||
EXPECT_THAT('\x4A', ecm_string[0]);
|
||||
EXPECT_THAT('\xD4', ecm_string[1]);
|
||||
EXPECT_THAT('\x02', ecm_string[2]); // version
|
||||
EXPECT_THAT(static_cast<int>(crypto_mode), ((ecm_string[3] >> 1) & 0xf));
|
||||
EXPECT_THAT(rotation_enabled ? 1 : 0, ecm_string[3] & 1);
|
||||
EXPECT_THAT(1, ((ecm_string[4] >> 7) & 1)); // wrapped key IV size, MB 1
|
||||
EXPECT_THAT(static_cast<int>(content_iv), ((ecm_string[4] >> 6) & 1));
|
||||
EXPECT_THAT(age_restriction, ((ecm_string[4] >> 1) & 0x1f));
|
||||
EXPECT_THAT(0, ecm_string[4] & 1); // zero padding
|
||||
}
|
||||
|
||||
EcmSerializerParams serializer_params_;
|
||||
};
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmDoubleKeySuccess) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
const std::string expected_key_fields = absl::StrCat(
|
||||
kEntitlementId, kContentKeyId, kWrappedContentKey, kWrappedKeyIv,
|
||||
kContentIv16Bytes, kEntitlementId2, kContentKeyId2, kWrappedContentKey2,
|
||||
kWrappedKeyIv2, kContentIv16Bytes2);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/true,
|
||||
kAgeRestriction, CryptoMode::kAesCtr, kIvSize16);
|
||||
EXPECT_EQ(serialized_ecm.substr(kEcmHeaderSize), expected_key_fields);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmSingleKeySuccess) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.odd_key = {};
|
||||
const std::string expected_key_fields =
|
||||
absl::StrCat(kEntitlementId, kContentKeyId, kWrappedContentKey,
|
||||
kWrappedKeyIv, kContentIv16Bytes);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/false,
|
||||
kAgeRestriction, CryptoMode::kAesCtr, kIvSize16);
|
||||
EXPECT_EQ(serialized_ecm.substr(kEcmHeaderSize), expected_key_fields);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmDoubleKey8ByteIvs) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv8Bytes;
|
||||
serializer_params_.odd_key.content_iv = kContentIv8Bytes2;
|
||||
const std::string expected_key_fields = absl::StrCat(
|
||||
kEntitlementId, kContentKeyId, kWrappedContentKey, kWrappedKeyIv,
|
||||
kContentIv8Bytes, kEntitlementId2, kContentKeyId2, kWrappedContentKey2,
|
||||
kWrappedKeyIv2, kContentIv8Bytes2);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/true,
|
||||
kAgeRestriction, CryptoMode::kAesCtr, kIvSize8);
|
||||
EXPECT_EQ(serialized_ecm.substr(kEcmHeaderSize), expected_key_fields);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmSingleKey8ByteIvs) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv8Bytes;
|
||||
serializer_params_.odd_key = {};
|
||||
const std::string expected_key_fields =
|
||||
absl::StrCat(kEntitlementId, kContentKeyId, kWrappedContentKey,
|
||||
kWrappedKeyIv, kContentIv8Bytes);
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/false,
|
||||
kAgeRestriction, CryptoMode::kAesCtr, kIvSize8);
|
||||
EXPECT_EQ(serialized_ecm.substr(kEcmHeaderSize), expected_key_fields);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmAgeRestriction) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.age_restriction = 18;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/true,
|
||||
serializer_params_.age_restriction,
|
||||
CryptoMode::kAesCtr, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmCasId) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.cas_id = 0x56C0;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ASSERT_GE(serialized_ecm.size(), 2);
|
||||
EXPECT_EQ((static_cast<uint8_t>(serialized_ecm[0]) << 8) |
|
||||
static_cast<uint8_t>(serialized_ecm[1]),
|
||||
serializer_params_.cas_id);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, SerializeEcmCryptoMode) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.crypto_mode = CryptoMode::kAesCbc;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
ValidateEcmHeaderFields(serialized_ecm, /*rotation_enabled=*/true,
|
||||
kAgeRestriction, CryptoMode::kAesCbc, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidCryptoModeFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.crypto_mode = CryptoMode::kInvalid;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, MismatchContentIvFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv16Bytes;
|
||||
serializer_params_.odd_key.content_iv = kContentIv8Bytes;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidEntitlementKeyIdFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.entitlement_key_id = "short_id";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidContentKeyIdFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.content_key_id = "short_id";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidWrappedKeyValueFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.wrapped_key_value = "short_v";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidWrappedKeyIvFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.wrapped_key_iv = "short_iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidContentIvFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = "iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidOddContentIvFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.odd_key.content_iv = "iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV2Test, InvalidOddKeyFail) {
|
||||
EcmSerializerV2 ecm_serializer;
|
||||
serializer_params_.odd_key = {};
|
||||
serializer_params_.odd_key.content_iv = kContentIv16Bytes;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
208
media_cas_packager_sdk/internal/ecm_serializer_v3.cc
Normal file
208
media_cas_packager_sdk/internal/ecm_serializer_v3.cc
Normal file
@@ -0,0 +1,208 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/ec_key.h"
|
||||
#include "common/hash_algorithm.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kEcmVersion = 3;
|
||||
constexpr int kEcmHeaderSize = 3;
|
||||
constexpr size_t kKeyIdSizeBytes = 16;
|
||||
constexpr size_t kKeyDataSizeBytes = 16;
|
||||
constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
constexpr size_t kContentIvSize8Bytes = 8;
|
||||
constexpr size_t kContentIvSize16Bytes = 16;
|
||||
|
||||
Status ValidateKeyInfo(const EcmSerializerKeyInfo& key_info, bool is_even_key) {
|
||||
// For even key, fields entitlement_key_id, wrapped_key_value and
|
||||
// wrapped_key_iv are required. For odd key, only wrapped_key_value field is
|
||||
// required,
|
||||
if ((is_even_key || !key_info.entitlement_key_id.empty()) &&
|
||||
(key_info.entitlement_key_id.size() != kKeyIdSizeBytes)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid entitlement_key_id size. Expect ",
|
||||
kKeyIdSizeBytes, " but got ",
|
||||
key_info.entitlement_key_id.size())};
|
||||
}
|
||||
if (key_info.wrapped_key_value.size() != kKeyDataSizeBytes) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid wrapped_key_value size. Expect ",
|
||||
kKeyDataSizeBytes, " but got ",
|
||||
key_info.wrapped_key_value.size())};
|
||||
}
|
||||
if ((is_even_key || !key_info.wrapped_key_iv.empty()) &&
|
||||
(key_info.wrapped_key_iv.size() != kWrappedKeyIvSizeBytes)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid wrapped_key_iv size. Expect ",
|
||||
kWrappedKeyIvSizeBytes, " but got ",
|
||||
key_info.wrapped_key_iv.size())};
|
||||
}
|
||||
if ((!key_info.content_iv.empty()) &&
|
||||
(key_info.content_iv.size() != kContentIvSize8Bytes) &&
|
||||
(key_info.content_iv.size() != kContentIvSize16Bytes)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid content_iv size. Expect ",
|
||||
kContentIvSize8Bytes, " or ", kContentIvSize16Bytes,
|
||||
" but got ", key_info.content_iv.size())};
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
EcmMetaData::CipherMode ConvertCryptoModeToProtoCipherMode(
|
||||
CryptoMode crypto_mode) {
|
||||
switch (crypto_mode) {
|
||||
case CryptoMode::kAesCbc:
|
||||
return EcmMetaData::AES_CBC;
|
||||
case CryptoMode::kAesCtr:
|
||||
return EcmMetaData::AES_CTR;
|
||||
case CryptoMode::kDvbCsa2:
|
||||
return EcmMetaData::DVB_CSA2;
|
||||
case CryptoMode::kDvbCsa3:
|
||||
return EcmMetaData::DVB_CSA3;
|
||||
case CryptoMode::kAesOfb:
|
||||
return EcmMetaData::AES_OFB;
|
||||
case CryptoMode::kAesScte:
|
||||
return EcmMetaData::AES_SCTE52;
|
||||
case CryptoMode::kInvalid:
|
||||
default:
|
||||
LOG(ERROR) << "Unknown crypto mode.";
|
||||
return EcmMetaData::UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EcmSerializerV3::SerializeEcm(const EcmSerializerParams& params,
|
||||
std::string* serialized_ecm) const {
|
||||
if (params.crypto_mode == CryptoMode::kInvalid) {
|
||||
return {error::INVALID_ARGUMENT, "Invalid crypo mode."};
|
||||
}
|
||||
Status status = ValidateKeyInfo(params.even_key, /*is_even_key=*/true);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Invalid even key info";
|
||||
return status;
|
||||
}
|
||||
bool has_odd_key = !params.odd_key.wrapped_key_value.empty();
|
||||
if (has_odd_key) {
|
||||
status = ValidateKeyInfo(params.odd_key, /*is_even_key=*/false);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Invalid odd key info";
|
||||
return status;
|
||||
}
|
||||
if (!params.odd_key.content_iv.empty() &&
|
||||
(params.odd_key.content_iv.size() !=
|
||||
params.even_key.content_iv.size())) {
|
||||
return {error::INVALID_ARGUMENT, "Content IV size must match."};
|
||||
}
|
||||
}
|
||||
|
||||
EcmPayload ecm_payload;
|
||||
EcmMetaData* meta_data = ecm_payload.mutable_meta_data();
|
||||
meta_data->set_cipher_mode(
|
||||
ConvertCryptoModeToProtoCipherMode(params.crypto_mode));
|
||||
if (params.age_restriction != 0) {
|
||||
meta_data->set_age_restriction(params.age_restriction);
|
||||
}
|
||||
|
||||
EcmKeyData* even_key_data = ecm_payload.mutable_even_key_data();
|
||||
even_key_data->set_entitlement_key_id(params.even_key.entitlement_key_id);
|
||||
even_key_data->set_wrapped_key_data(params.even_key.wrapped_key_value);
|
||||
even_key_data->set_wrapped_key_iv(params.even_key.wrapped_key_iv);
|
||||
// If content_iv is not present, a default value (all 0) will be assumed at
|
||||
// the plugin side. This is usually used in CSA2 encryption mode.
|
||||
if (!params.even_key.content_iv.empty()) {
|
||||
even_key_data->set_content_iv(params.even_key.content_iv);
|
||||
}
|
||||
|
||||
if (has_odd_key) {
|
||||
EcmKeyData* odd_key_data = ecm_payload.mutable_odd_key_data();
|
||||
odd_key_data->set_wrapped_key_data(params.odd_key.wrapped_key_value);
|
||||
// Only put these fields if they are different from the even key.
|
||||
if (!params.odd_key.entitlement_key_id.empty() &&
|
||||
(params.odd_key.entitlement_key_id !=
|
||||
params.even_key.entitlement_key_id)) {
|
||||
odd_key_data->set_entitlement_key_id(params.odd_key.entitlement_key_id);
|
||||
}
|
||||
if (!params.odd_key.wrapped_key_iv.empty() &&
|
||||
(params.odd_key.wrapped_key_iv != params.even_key.wrapped_key_iv)) {
|
||||
odd_key_data->set_wrapped_key_iv(params.odd_key.wrapped_key_iv);
|
||||
}
|
||||
if (!params.odd_key.content_iv.empty() &&
|
||||
(params.odd_key.content_iv != params.even_key.content_iv)) {
|
||||
odd_key_data->set_content_iv(params.odd_key.content_iv);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.fingerprinting.has_value()) {
|
||||
ecm_payload.mutable_fingerprinting()->set_control(
|
||||
params.fingerprinting->control);
|
||||
}
|
||||
if (params.service_blocking.has_value()) {
|
||||
for (const auto& device_group : params.service_blocking->device_groups) {
|
||||
ecm_payload.mutable_service_blocking()->add_device_groups(device_group);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string serialized_ecm_payload = ecm_payload.SerializeAsString();
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(serialized_ecm_payload);
|
||||
|
||||
// Sign the serialized ecm payload if the private key is available.
|
||||
if (!params.ecc_private_signing_key.empty()) {
|
||||
absl::StatusOr<std::string> result = CalculateSignature(
|
||||
params.ecc_private_signing_key, serialized_ecm_payload);
|
||||
if (!result.ok()) {
|
||||
LOG(ERROR) << "Failed to sign ECM: " << result.status();
|
||||
return Status(error::INTERNAL, "Failed to sign ECM.");
|
||||
}
|
||||
signed_ecm_payload.set_signature(result.value());
|
||||
}
|
||||
|
||||
// Put ECM header.
|
||||
serialized_ecm->reserve(kEcmHeaderSize);
|
||||
serialized_ecm->push_back(static_cast<char>(params.cas_id >> 8));
|
||||
serialized_ecm->push_back(static_cast<char>(params.cas_id & 0xFF));
|
||||
serialized_ecm->push_back(static_cast<char>(kEcmVersion));
|
||||
// Put ECM payload.
|
||||
signed_ecm_payload.AppendToString(serialized_ecm);
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> EcmSerializerV3::CalculateSignature(
|
||||
absl::string_view private_signing_key, absl::string_view message) const {
|
||||
std::unique_ptr<ECPrivateKey> ec_private_key =
|
||||
ECPrivateKey::Create(std::string(private_signing_key));
|
||||
if (ec_private_key == nullptr) {
|
||||
return absl::InternalError("Failed to construct a ECPrivateKey.");
|
||||
}
|
||||
std::string signature;
|
||||
if (!ec_private_key->GenerateSignature(std::string(message),
|
||||
HashAlgorithm::kSha256, &signature) ||
|
||||
signature.empty()) {
|
||||
return absl::InternalError("Failed to generate signature.");
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
46
media_cas_packager_sdk/internal/ecm_serializer_v3.h
Normal file
46
media_cas_packager_sdk/internal/ecm_serializer_v3.h
Normal file
@@ -0,0 +1,46 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V3_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V3_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/status/statusor.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Generates serialized ECM version 3.
|
||||
class EcmSerializerV3 : public EcmSerializer {
|
||||
public:
|
||||
EcmSerializerV3() = default;
|
||||
EcmSerializerV3(const EcmSerializerV3&) = delete;
|
||||
EcmSerializerV3& operator=(const EcmSerializerV3&) = delete;
|
||||
~EcmSerializerV3() override = default;
|
||||
|
||||
Status SerializeEcm(const EcmSerializerParams& params,
|
||||
std::string* serialized_ecm) const override;
|
||||
|
||||
EcmSerializerVersion Version() const override {
|
||||
return EcmSerializerVersion::kV3;
|
||||
}
|
||||
|
||||
private:
|
||||
// Calculates signature of |message| using ECC |private_signing_key|.
|
||||
// Returns the generated signature on success, and an error status otherwise.
|
||||
absl::StatusOr<std::string> CalculateSignature(
|
||||
absl::string_view private_signing_key, absl::string_view message) const;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_SERIALIZER_V3_H_
|
||||
412
media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc
Normal file
412
media_cas_packager_sdk/internal/ecm_serializer_v3_test.cc
Normal file
@@ -0,0 +1,412 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2020 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer_v3.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "common/ec_key.h"
|
||||
#include "common/ec_test_keys.h"
|
||||
#include "common/hash_algorithm.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
namespace {
|
||||
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Not;
|
||||
|
||||
constexpr int kEcmVersion = 3;
|
||||
constexpr uint16_t kWvCasCaSystemId = 0x4AD4;
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2";
|
||||
constexpr char kEntitlementId[] = "entitlement_id..";
|
||||
constexpr char kEntitlementId2[] = "entitlement_id.2";
|
||||
constexpr char kContentKeyId[] = "content_key_id..";
|
||||
constexpr char kContentKeyId2[] = "content_key_id.2";
|
||||
constexpr char kContentIv8Bytes[] = "c_iv....";
|
||||
constexpr char kContentIv8Bytes2[] = "c_iv...2";
|
||||
constexpr char kContentIv16Bytes[] = "c_iv....c_iv....";
|
||||
constexpr char kContentIv16Bytes2[] = "c_iv....c_iv...2";
|
||||
constexpr char kWrappedContentKey[] = "wrapped_key.....";
|
||||
constexpr char kWrappedContentKey2[] = "wrapped_key....2";
|
||||
|
||||
constexpr int kAgeRestriction = 0;
|
||||
constexpr int kEcmProtoStartOffset = 3;
|
||||
|
||||
class EcmSerializerV3Test : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
serializer_params_.cas_id = kWvCasCaSystemId;
|
||||
serializer_params_.crypto_mode = CryptoMode::kAesCtr;
|
||||
serializer_params_.age_restriction = kAgeRestriction;
|
||||
serializer_params_.even_key = {kEntitlementId, kContentKeyId,
|
||||
kWrappedContentKey, kWrappedKeyIv,
|
||||
kContentIv16Bytes};
|
||||
serializer_params_.odd_key = {kEntitlementId2, kContentKeyId2,
|
||||
kWrappedContentKey2, kWrappedKeyIv2,
|
||||
kContentIv16Bytes2};
|
||||
}
|
||||
|
||||
void VerifyHeader(const std::string& serialized_ecm, uint16_t expected_cas_id,
|
||||
uint8_t expected_version) {
|
||||
ASSERT_GE(serialized_ecm.size(), kEcmProtoStartOffset);
|
||||
EXPECT_EQ((static_cast<uint8_t>(serialized_ecm[0]) << 8) |
|
||||
static_cast<uint8_t>(serialized_ecm[1]),
|
||||
expected_cas_id);
|
||||
EXPECT_EQ(serialized_ecm[2], expected_version);
|
||||
}
|
||||
|
||||
void VerifyMetaData(const EcmMetaData meta_data,
|
||||
EcmMetaData::CipherMode expected_cipher_mode,
|
||||
int expected_age_restriction) {
|
||||
EXPECT_EQ(meta_data.cipher_mode(), expected_cipher_mode);
|
||||
if (expected_age_restriction == 0) {
|
||||
EXPECT_FALSE(meta_data.has_age_restriction());
|
||||
}
|
||||
EXPECT_EQ(meta_data.age_restriction(), expected_age_restriction);
|
||||
}
|
||||
|
||||
void VerifyEcmKeyData(const EcmKeyData& ecm_key_data,
|
||||
const EcmSerializerKeyInfo& key_info) {
|
||||
EXPECT_EQ(ecm_key_data.content_iv(), key_info.content_iv);
|
||||
EXPECT_EQ(ecm_key_data.entitlement_key_id(), key_info.entitlement_key_id);
|
||||
EXPECT_EQ(ecm_key_data.wrapped_key_data(), key_info.wrapped_key_value);
|
||||
EXPECT_EQ(ecm_key_data.wrapped_key_iv(), key_info.wrapped_key_iv);
|
||||
}
|
||||
|
||||
EcmSerializerParams serializer_params_;
|
||||
};
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmDoubleKeySuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
VerifyHeader(serialized_ecm, kWvCasCaSystemId, kEcmVersion);
|
||||
SignedEcmPayload signed_payload;
|
||||
ASSERT_TRUE(signed_payload.ParseFromString(
|
||||
serialized_ecm.substr(kEcmProtoStartOffset)));
|
||||
EXPECT_THAT(signed_payload.signature(), IsEmpty());
|
||||
EcmPayload payload;
|
||||
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
|
||||
VerifyMetaData(payload.meta_data(), EcmMetaData::AES_CTR, kAgeRestriction);
|
||||
VerifyEcmKeyData(payload.even_key_data(), serializer_params_.even_key);
|
||||
VerifyEcmKeyData(payload.odd_key_data(), serializer_params_.odd_key);
|
||||
EXPECT_FALSE(payload.has_fingerprinting());
|
||||
EXPECT_FALSE(payload.has_service_blocking());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmSingleKeySuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.odd_key = {};
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
VerifyHeader(serialized_ecm, kWvCasCaSystemId, kEcmVersion);
|
||||
SignedEcmPayload signed_payload;
|
||||
ASSERT_TRUE(signed_payload.ParseFromString(
|
||||
serialized_ecm.substr(kEcmProtoStartOffset)));
|
||||
EcmPayload payload;
|
||||
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
|
||||
VerifyMetaData(payload.meta_data(), EcmMetaData::AES_CTR, kAgeRestriction);
|
||||
VerifyEcmKeyData(payload.even_key_data(), serializer_params_.even_key);
|
||||
EXPECT_FALSE(payload.has_odd_key_data());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmOmittedFieldsSuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
// odd_key has the same content as even_key.
|
||||
serializer_params_.odd_key = serializer_params_.even_key;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
VerifyHeader(serialized_ecm, kWvCasCaSystemId, kEcmVersion);
|
||||
SignedEcmPayload signed_payload;
|
||||
ASSERT_TRUE(signed_payload.ParseFromString(
|
||||
serialized_ecm.substr(kEcmProtoStartOffset)));
|
||||
EcmPayload payload;
|
||||
ASSERT_TRUE(payload.ParseFromString(signed_payload.serialized_payload()));
|
||||
VerifyMetaData(payload.meta_data(), EcmMetaData::AES_CTR, kAgeRestriction);
|
||||
VerifyEcmKeyData(payload.even_key_data(), serializer_params_.even_key);
|
||||
// Fields content_iv, entitlement id/iv will be omitted in the odd key.
|
||||
EXPECT_FALSE(payload.odd_key_data().has_content_iv());
|
||||
EXPECT_FALSE(payload.odd_key_data().has_entitlement_key_id());
|
||||
EXPECT_FALSE(payload.odd_key_data().has_wrapped_key_iv());
|
||||
EXPECT_EQ(payload.odd_key_data().wrapped_key_data(),
|
||||
serializer_params_.odd_key.wrapped_key_value);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmOmitContentIvSuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv.clear();
|
||||
serializer_params_.odd_key.content_iv.clear();
|
||||
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()));
|
||||
VerifyEcmKeyData(payload.even_key_data(), serializer_params_.even_key);
|
||||
VerifyEcmKeyData(payload.odd_key_data(), serializer_params_.odd_key);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmOmitOddKeyFieldsSuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.odd_key.entitlement_key_id.clear();
|
||||
serializer_params_.odd_key.wrapped_key_iv.clear();
|
||||
serializer_params_.odd_key.content_iv.clear();
|
||||
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()));
|
||||
VerifyEcmKeyData(payload.even_key_data(), serializer_params_.even_key);
|
||||
EXPECT_FALSE(payload.odd_key_data().has_content_iv());
|
||||
EXPECT_FALSE(payload.odd_key_data().has_entitlement_key_id());
|
||||
EXPECT_FALSE(payload.odd_key_data().has_wrapped_key_iv());
|
||||
EXPECT_EQ(payload.odd_key_data().wrapped_key_data(),
|
||||
serializer_params_.odd_key.wrapped_key_value);
|
||||
}
|
||||
|
||||
class SerializeEcmAgeRestrictionTest
|
||||
: public EcmSerializerV3Test,
|
||||
public testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(SerializeEcmAgeRestrictionTest, ExpectedAgeRestriction) {
|
||||
uint8_t expected_age_restriction = GetParam();
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.age_restriction = expected_age_restriction;
|
||||
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()));
|
||||
VerifyMetaData(payload.meta_data(), EcmMetaData::AES_CTR,
|
||||
expected_age_restriction);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(SerializeEcmAgeRestrictionTest,
|
||||
SerializeEcmAgeRestrictionTest,
|
||||
testing::Values(0, 3, 18));
|
||||
|
||||
class SerializeEcmCipherModeTest
|
||||
: public EcmSerializerV3Test,
|
||||
public testing::WithParamInterface<
|
||||
testing::tuple<CryptoMode, EcmMetaData::CipherMode>> {};
|
||||
|
||||
TEST_P(SerializeEcmCipherModeTest, ExpectedCipherMode) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.crypto_mode = std::get<0>(GetParam());
|
||||
EcmMetaData::CipherMode expected_cipher_mode = std::get<1>(GetParam());
|
||||
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()));
|
||||
VerifyMetaData(payload.meta_data(), expected_cipher_mode, kAgeRestriction);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
SerializeEcmCipherModeTest, SerializeEcmCipherModeTest,
|
||||
testing::Values(
|
||||
std::make_tuple(CryptoMode::kAesCbc, EcmMetaData::AES_CBC),
|
||||
std::make_tuple(CryptoMode::kAesCtr, EcmMetaData::AES_CTR),
|
||||
std::make_tuple(CryptoMode::kDvbCsa2, EcmMetaData::DVB_CSA2),
|
||||
std::make_tuple(CryptoMode::kDvbCsa3, EcmMetaData::DVB_CSA3),
|
||||
std::make_tuple(CryptoMode::kAesOfb, EcmMetaData::AES_OFB),
|
||||
std::make_tuple(CryptoMode::kAesScte, EcmMetaData::AES_SCTE52)));
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmDoubleKey8ByteIvs) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv8Bytes;
|
||||
serializer_params_.odd_key.content_iv = kContentIv8Bytes2;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmSingleKey8ByteIvs) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv8Bytes;
|
||||
serializer_params_.odd_key = {};
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, SerializeEcmCasId) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
const uint16_t expected_cas_id = 0x56C0;
|
||||
serializer_params_.cas_id = expected_cas_id;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_OK(ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm));
|
||||
|
||||
VerifyHeader(serialized_ecm, expected_cas_id, kEcmVersion);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidCryptoModeFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.crypto_mode = CryptoMode::kInvalid;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, MismatchContentIvFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = kContentIv16Bytes;
|
||||
serializer_params_.odd_key.content_iv = kContentIv8Bytes;
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidEntitlementKeyIdFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.entitlement_key_id = "short_id";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidWrappedKeyValueFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.wrapped_key_value = "short_v";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidWrappedKeyIvFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.wrapped_key_iv = "short_iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidContentIvFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.even_key.content_iv = "iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, InvalidOddContentIvFail) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.odd_key.content_iv = "iv";
|
||||
std::string serialized_ecm;
|
||||
|
||||
ASSERT_FALSE(
|
||||
ecm_serializer.SerializeEcm(serializer_params_, &serialized_ecm).ok());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, FingerprintingSuccess) {
|
||||
const std::string expected_control = "control";
|
||||
Fingerprinting expected_proto;
|
||||
expected_proto.set_control(expected_control);
|
||||
EcmFingerprintingParams fingerprinting;
|
||||
fingerprinting.control = expected_control;
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.fingerprinting = absl::make_optional(fingerprinting);
|
||||
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()));
|
||||
// Use testing::EqualsProto once available in external googletest repo.
|
||||
EXPECT_EQ(payload.fingerprinting().SerializeAsString(),
|
||||
expected_proto.SerializeAsString());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, ServiceBlockingSuccess) {
|
||||
const std::vector<std::string> expected_device_groups = {"group1", "group2"};
|
||||
ServiceBlocking expected_proto;
|
||||
for (const auto& expected_device_group : expected_device_groups) {
|
||||
expected_proto.add_device_groups(expected_device_group);
|
||||
}
|
||||
EcmServiceBlockingParams service_blocking;
|
||||
service_blocking.device_groups = expected_device_groups;
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
serializer_params_.service_blocking = absl::make_optional(service_blocking);
|
||||
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()));
|
||||
// Use testing::EqualsProto once available in external googletest repo.
|
||||
EXPECT_EQ(payload.service_blocking().SerializeAsString(),
|
||||
expected_proto.SerializeAsString());
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializerV3Test, GenerateSignatureSuccess) {
|
||||
EcmSerializerV3 ecm_serializer;
|
||||
ECTestKeys test_keys;
|
||||
serializer_params_.ecc_private_signing_key =
|
||||
test_keys.private_test_key_1_secp256r1();
|
||||
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)));
|
||||
EXPECT_THAT(signed_payload.signature(), Not(IsEmpty()));
|
||||
// Check the signature can be verified.
|
||||
std::unique_ptr<ECPublicKey> public_key(
|
||||
ECPublicKey::Create(test_keys.public_test_key_1_secp256r1()));
|
||||
ASSERT_TRUE(public_key != nullptr);
|
||||
EXPECT_TRUE(public_key->VerifySignature(signed_payload.serialized_payload(),
|
||||
HashAlgorithm::kSha256,
|
||||
signed_payload.signature()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
@@ -10,32 +10,31 @@
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm_serializer.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
using ::testing::Return;
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// TODO(user): Encryption options yield a small number of possible sizes
|
||||
// for ECMS - Explain the distinct cases and add/use literals for each one.
|
||||
|
||||
namespace {
|
||||
|
||||
using ::testing::DoAll;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
|
||||
constexpr uint16_t kWvCasCaSystemId = 0x4AD4;
|
||||
constexpr uint16_t kWidevineNewSystemIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
|
||||
constexpr char kTrackTypeSD[] = "SD";
|
||||
constexpr char kTrackTypeHD[] = "HD";
|
||||
constexpr char kTrackTypeAUDIO[] = "AUDIO";
|
||||
constexpr char kTrackTypeBAD[] = "BAD";
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kEntitlementKeyId[] = "entitlement_Mock";
|
||||
constexpr char kWrappedKeyValue[] = "MockMockMockMock";
|
||||
|
||||
constexpr size_t kEcmHeaderSize = 5;
|
||||
constexpr size_t kEcmKeyIdSize = 16;
|
||||
constexpr size_t kEcmKeyDataSize = 16;
|
||||
constexpr size_t kEcmIvSize16 = 16;
|
||||
constexpr size_t kEcmIvSize8 = 8;
|
||||
|
||||
constexpr size_t kEcmKeyInfoSize =
|
||||
kEcmKeyIdSize + kEcmKeyIdSize + kEcmKeyDataSize;
|
||||
constexpr size_t kEcmVersionIndex = 2;
|
||||
|
||||
// ECM payload data taken from a CETS encrypted file at Google Fiber
|
||||
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
|
||||
@@ -76,10 +75,6 @@ constexpr char kExpectedEcmPacket[] = {
|
||||
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
|
||||
'\xFF', '\xFF', '\xFF'};
|
||||
|
||||
size_t IvExpectedSize(EcmIvSize iv_size) {
|
||||
return (iv_size == kIvSize8) ? kEcmIvSize8 : kEcmIvSize16;
|
||||
}
|
||||
|
||||
void InitEntitledKey(EntitledKeyInfo* key, const std::string& id,
|
||||
const std::string& value, const std::string& iv) {
|
||||
key->key_id = id;
|
||||
@@ -88,44 +83,6 @@ void InitEntitledKey(EntitledKeyInfo* key, const std::string& id,
|
||||
key->wrapped_key_iv = kWrappedKeyIv;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MockEcm : public Ecm {
|
||||
public:
|
||||
MockEcm() = default;
|
||||
~MockEcm() override = default;
|
||||
|
||||
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
|
||||
MOCK_METHOD(CryptoMode, crypto_mode, (), (const, override));
|
||||
MOCK_METHOD(bool, paired_keys_required, (), (const, override));
|
||||
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
|
||||
|
||||
std::string CallSerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return SerializeEcm(keys);
|
||||
}
|
||||
|
||||
virtual Status MockWrapEntitledKeys(
|
||||
const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = "entitlement_Mock";
|
||||
entitled_key->wrapped_key_value = "MockMockMockMock";
|
||||
entitled_key->wrapped_key_iv = "WRAPPED_KIV....x";
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
void MockSetup(bool two_keys, const std::string& track_type,
|
||||
uint8_t age_restriction, CryptoMode crypto_mode,
|
||||
size_t civ_size) {
|
||||
EXPECT_CALL(*this, age_restriction())
|
||||
.WillRepeatedly(Return(age_restriction));
|
||||
EXPECT_CALL(*this, crypto_mode()).WillRepeatedly(Return(crypto_mode));
|
||||
EXPECT_CALL(*this, paired_keys_required()).WillRepeatedly(Return(two_keys));
|
||||
EXPECT_CALL(*this, content_iv_size()).WillRepeatedly(Return(civ_size));
|
||||
}
|
||||
};
|
||||
|
||||
class EcmTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
@@ -135,10 +92,6 @@ class EcmTest : public testing::Test {
|
||||
"ct_iv..2");
|
||||
InitEntitledKey(&valid3_iv_16_16_, "keyid_16b......3", "keyvalue_16b...3",
|
||||
"content_iv.....3");
|
||||
InitEntitledKey(&valid4_iv_16_16_, "keyid_16b....xx4", "keyvalue_16b...4",
|
||||
"content_iv.....4");
|
||||
InitEntitledKey(&invalid1_key_id_iv_16_8_, "keyid_12b..1",
|
||||
"keyvalue_16b...1", "ct_iv..5");
|
||||
|
||||
params_one_key_.key_rotation_enabled = false;
|
||||
params_two_keys_.key_rotation_enabled = true;
|
||||
@@ -156,54 +109,11 @@ class EcmTest : public testing::Test {
|
||||
EcmInitParameters params_one_key_;
|
||||
EcmInitParameters params_two_keys_;
|
||||
EcmInitParameters params_simple_;
|
||||
EcmInitParameters params_default_;
|
||||
EntitlementKeyInfo injected_entitlement_one_;
|
||||
EntitlementKeyInfo injected_entitlement_two_;
|
||||
EntitledKeyInfo valid1_iv_16_8_;
|
||||
EntitledKeyInfo valid2_iv_16_8_;
|
||||
EntitledKeyInfo valid3_iv_16_16_;
|
||||
EntitledKeyInfo valid4_iv_16_16_;
|
||||
EntitledKeyInfo invalid1_key_id_iv_16_8_;
|
||||
};
|
||||
|
||||
class EcmSerializeEcmTest : public EcmTest {
|
||||
public:
|
||||
void ValidateEcmHeaderFields(const std::string& ecm_string,
|
||||
bool rotation_enabled, int age_restriction,
|
||||
CryptoMode crypto_mode, EcmIvSize content_iv) {
|
||||
EXPECT_THAT('\x4A', ecm_string[0]);
|
||||
EXPECT_THAT('\xD4', ecm_string[1]);
|
||||
EXPECT_THAT('\x02', ecm_string[2]); // version
|
||||
EXPECT_THAT(static_cast<int>(crypto_mode), ((ecm_string[3] >> 1) & 0xf));
|
||||
EXPECT_THAT(rotation_enabled ? 1 : 0, ecm_string[3] & 1);
|
||||
EXPECT_THAT(1, ((ecm_string[4] >> 7) & 1)); // wrapped key IV size, MB 1
|
||||
EXPECT_THAT(static_cast<int>(content_iv), ((ecm_string[4] >> 6) & 1));
|
||||
EXPECT_THAT(age_restriction, ((ecm_string[4] >> 1) & 0x1f));
|
||||
EXPECT_THAT(0, ecm_string[4] & 1); // zero padding
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsOneKey(const std::string& buf_string,
|
||||
int age_restriction, CryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size = kEcmHeaderSize + kEcmKeyInfoSize + kEcmIvSize16 +
|
||||
IvExpectedSize(content_iv);
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, false, age_restriction, crypto_mode,
|
||||
content_iv);
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsTwoKeys(const std::string& buf_string,
|
||||
int age_restriction, CryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size =
|
||||
kEcmHeaderSize +
|
||||
(2 * (kEcmKeyInfoSize + kEcmIvSize16 + IvExpectedSize(content_iv)));
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, true, age_restriction, crypto_mode,
|
||||
content_iv);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmNotInitialized) {
|
||||
@@ -268,15 +178,55 @@ TEST_F(EcmTest, InitIvSize16x16OK) {
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyOK) {
|
||||
class EcmInitWithValidCasIdTest : public EcmTest,
|
||||
public testing::WithParamInterface<uint16_t> {};
|
||||
TEST_P(EcmInitWithValidCasIdTest, InitWithValidCasIdSucceed) {
|
||||
params_simple_.cas_id = GetParam();
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
EXPECT_OK(ecm_gen.Initialize(
|
||||
params_simple_, {injected_entitlement_one_, injected_entitlement_two_}));
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(EcmInitWithValidCasIdTest, EcmInitWithValidCasIdTest,
|
||||
testing::Values(kWvCasCaSystemId,
|
||||
kWidevineNewSystemIdLowerBound, 0x56C5,
|
||||
kWidevineNewSystemIdUpperBound));
|
||||
|
||||
class EcmInitWithInvalidCasIdTest : public EcmTest,
|
||||
public testing::WithParamInterface<uint16_t> {
|
||||
};
|
||||
TEST_P(EcmInitWithInvalidCasIdTest, InitWithInvalidCasIdFail) {
|
||||
params_simple_.cas_id = GetParam();
|
||||
Ecm ecm_gen;
|
||||
EXPECT_EQ(ecm_gen.Initialize(params_simple_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}),
|
||||
Status(error::INVALID_ARGUMENT, "Invalid CA system ID."));
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(EcmInitWithInvalidCasIdTest,
|
||||
EcmInitWithInvalidCasIdTest,
|
||||
testing::Values(0, 0xABCD,
|
||||
kWidevineNewSystemIdLowerBound - 1,
|
||||
kWidevineNewSystemIdUpperBound + 1));
|
||||
|
||||
class EcmSerializerVersionTest
|
||||
: public EcmTest,
|
||||
public testing::WithParamInterface<EcmVersion> {};
|
||||
TEST_P(EcmSerializerVersionTest, EcmSerializerOk) {
|
||||
Ecm ecm_gen;
|
||||
EcmInitParameters params;
|
||||
params.key_rotation_enabled = false;
|
||||
params.ecm_version = GetParam();
|
||||
|
||||
ASSERT_OK(ecm_gen.Initialize(params, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(
|
||||
ecm_gen.GenerateSingleKeyEcm(&valid3_iv_16_16_, kTrackTypeSD, &ecm));
|
||||
ASSERT_GT(ecm.size(), kEcmVersionIndex);
|
||||
EXPECT_EQ(ecm[kEcmVersionIndex],
|
||||
params.ecm_version == EcmVersion::kV2 ? 2 : 3);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(EcmSerializerVersionTest, EcmSerializerVersionTest,
|
||||
testing::Values(EcmVersion::kV2, EcmVersion::kV3));
|
||||
|
||||
TEST_F(EcmTest, GenerateWithBadTrackType) {
|
||||
Ecm ecm_gen;
|
||||
@@ -291,183 +241,132 @@ TEST_F(EcmTest, GenerateWithBadTrackType) {
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeHD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysOK) {
|
||||
Ecm ecm_gen;
|
||||
InitParams(¶ms_two_keys_, kIvSize8);
|
||||
class MockEcmSerializer : public EcmSerializer {
|
||||
public:
|
||||
MockEcmSerializer() = default;
|
||||
~MockEcmSerializer() override = default;
|
||||
|
||||
MOCK_METHOD(Status, SerializeEcm,
|
||||
(const EcmSerializerParams& params, std::string* serialized_ecm),
|
||||
(const, override));
|
||||
MOCK_METHOD(EcmSerializerVersion, Version, (), (const, override));
|
||||
};
|
||||
|
||||
class FakeEcm : public Ecm {
|
||||
public:
|
||||
FakeEcm() = default;
|
||||
~FakeEcm() override = default;
|
||||
|
||||
void SetEcmSerializer(
|
||||
std::unique_ptr<EcmSerializer> ecm_serializer) override {
|
||||
return Ecm::SetEcmSerializer(std::move(ecm_serializer));
|
||||
}
|
||||
|
||||
Status WrapEntitledKeys(
|
||||
const std::string& track_type,
|
||||
const std::vector<EntitledKeyInfo*>& keys) const override {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = kEntitlementKeyId;
|
||||
entitled_key->wrapped_key_value = kWrappedKeyValue;
|
||||
}
|
||||
return OkStatus();
|
||||
}
|
||||
};
|
||||
|
||||
bool IsEcmSerializerKeyInfoEq(EcmSerializerKeyInfo a, EcmSerializerKeyInfo b) {
|
||||
return (a.entitlement_key_id == b.entitlement_key_id) &&
|
||||
(a.content_key_id == b.content_key_id) &&
|
||||
(a.wrapped_key_value == b.wrapped_key_value) &&
|
||||
(a.wrapped_key_iv == b.wrapped_key_iv) &&
|
||||
(a.content_iv == b.content_iv);
|
||||
}
|
||||
|
||||
MATCHER_P(EcmSerializerParamsEq, expected, "") {
|
||||
return (arg.cas_id == expected.cas_id) &&
|
||||
(arg.crypto_mode == expected.crypto_mode) &&
|
||||
(arg.age_restriction == expected.age_restriction) &&
|
||||
IsEcmSerializerKeyInfoEq(arg.even_key, expected.even_key) &&
|
||||
IsEcmSerializerKeyInfoEq(arg.odd_key, expected.odd_key);
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmTwoKeysOK) {
|
||||
FakeEcm ecm_gen;
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
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_;
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EcmSerializerParams expected_params;
|
||||
expected_params.cas_id = kWvCasCaSystemId;
|
||||
expected_params.age_restriction = 0;
|
||||
expected_params.crypto_mode = CryptoMode::kAesCtr;
|
||||
expected_params.even_key = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue,
|
||||
key1.wrapped_key_iv, key1.content_iv};
|
||||
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue,
|
||||
key2.wrapped_key_iv, key2.content_iv};
|
||||
std::string expected_serialized_ecm = "expected_serialized_ecm";
|
||||
EXPECT_CALL(*ecm_serializer_pointer,
|
||||
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
|
||||
.WillOnce(
|
||||
DoAll(SetArgPointee<1>(expected_serialized_ecm), Return(OkStatus())));
|
||||
std::string actual_generated_ecm;
|
||||
|
||||
ASSERT_OK(
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &actual_generated_ecm));
|
||||
|
||||
EXPECT_EQ(actual_generated_ecm, expected_serialized_ecm);
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = invalid1_key_id_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(
|
||||
error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateOneKeyWrong) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_one_key_, {injected_entitlement_one_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(77, ecm.size());
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
Ecm ecm_gen;
|
||||
TEST_F(EcmTest, GenerateEcmParamsPassedDown) {
|
||||
FakeEcm ecm_gen;
|
||||
EcmInitParameters params;
|
||||
params.age_restriction = 3;
|
||||
params.cas_id = 0x56C0;
|
||||
params.crypto_mode = CryptoMode::kAesCbc;
|
||||
params.ecc_private_signing_key = "key";
|
||||
params.key_rotation_enabled = true;
|
||||
params.content_iv_size = kIvSize8;
|
||||
ASSERT_OK(ecm_gen.Initialize(
|
||||
params, {injected_entitlement_one_, injected_entitlement_two_}));
|
||||
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_;
|
||||
// IV size mismatch.
|
||||
InitParams(¶ms_two_keys_, kIvSize16);
|
||||
std::string request;
|
||||
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 = {kEntitlementKeyId, key1.key_id, kWrappedKeyValue,
|
||||
key1.wrapped_key_iv, key1.content_iv};
|
||||
expected_params.odd_key = {kEntitlementKeyId, key2.key_id, kWrappedKeyValue,
|
||||
key2.wrapped_key_iv, key2.content_iv};
|
||||
EXPECT_CALL(*ecm_serializer_pointer,
|
||||
SerializeEcm(EcmSerializerParamsEq(expected_params), NotNull()))
|
||||
.WillOnce(Return(OkStatus()));
|
||||
std::string generated_ecm;
|
||||
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &generated_ecm));
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateEcmSerializeErrorOK) {
|
||||
FakeEcm ecm_gen;
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
auto ecm_serializer = absl::make_unique<MockEcmSerializer>();
|
||||
const MockEcmSerializer* ecm_serializer_pointer = ecm_serializer.get();
|
||||
ecm_gen.SetEcmSerializer(std::move(ecm_serializer));
|
||||
Status expected_status = {error::INVALID_ARGUMENT, "err"};
|
||||
EXPECT_CALL(*ecm_serializer_pointer, SerializeEcm)
|
||||
.WillOnce(Return(expected_status));
|
||||
std::string ecm;
|
||||
EXPECT_EQ(error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
InitParams(¶ms_two_keys_, kIvSize8);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
}
|
||||
|
||||
TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) {
|
||||
Ecm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
InitParams(¶ms_two_keys_, kIvSize16);
|
||||
ASSERT_OK(ecm_gen.Initialize(params_two_keys_, {injected_entitlement_one_,
|
||||
injected_entitlement_two_}));
|
||||
|
||||
std::string ecm;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm));
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
}
|
||||
|
||||
// TODO(user): Add more unit tests for error paths around SerializeEcm.
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, 0, CryptoMode::kAesCtr, 16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
|
||||
ecm_gen.MockSetup(false, kTrackTypeSD, 0, CryptoMode::kAesCtr, 16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, 0, CryptoMode::kAesCtr, 8);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
|
||||
ecm_gen.MockSetup(false, kTrackTypeSD, 0, CryptoMode::kAesCtr, 8);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8);
|
||||
}
|
||||
|
||||
TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) {
|
||||
MockEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
uint8_t age_restriction = 18;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, age_restriction, CryptoMode::kAesCtr,
|
||||
16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
EXPECT_CALL(ecm_gen, age_restriction())
|
||||
.WillRepeatedly(Return(age_restriction));
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr,
|
||||
kIvSize16);
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr,
|
||||
kIvSize16);
|
||||
EXPECT_EQ(ecm_gen.GenerateEcm(&valid1_iv_16_8_, &valid2_iv_16_8_,
|
||||
kTrackTypeSD, &ecm),
|
||||
expected_status);
|
||||
}
|
||||
|
||||
class EcmTsPacketTest : public ::testing::Test {};
|
||||
@@ -477,9 +376,14 @@ TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId80) {
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc, packet));
|
||||
ssize_t bytes_modified = 0;
|
||||
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc, packet,
|
||||
&bytes_modified));
|
||||
|
||||
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
EXPECT_EQ(bytes_modified, 188);
|
||||
}
|
||||
|
||||
TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId81) {
|
||||
@@ -487,13 +391,19 @@ TEST_F(EcmTsPacketTest, GenerateTsPacket_TableId81) {
|
||||
ProgramId pid = 0x1FFD;
|
||||
ContinuityCounter cc = 0;
|
||||
uint8_t packet[188];
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc, packet));
|
||||
ssize_t bytes_modified = 0;
|
||||
|
||||
EXPECT_OK(Ecm::GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc, packet,
|
||||
&bytes_modified));
|
||||
|
||||
char expected_ecm[188];
|
||||
memcpy(expected_ecm, kExpectedEcmPacket, 188);
|
||||
expected_ecm[5] = '\x81';
|
||||
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
|
||||
EXPECT_EQ(1, cc);
|
||||
EXPECT_EQ(bytes_modified, 188);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
@@ -21,6 +23,7 @@
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
#include "media_cas_packager_sdk/internal/util.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
|
||||
// CA System ID for Widevine.
|
||||
static constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
@@ -33,7 +36,7 @@ static constexpr uint16_t kWidevineNewSystemIdUpperBound = 0x56C9;
|
||||
// TODO(user): Understand the difference between the two formats.
|
||||
static constexpr uint8_t kSectionTSpktFlag = 0x01;
|
||||
// 'max_stream' parameter defines the max number of simultaneous opened streams
|
||||
// suppported by an ECMG on a channel.
|
||||
// supported by an ECMG on a channel.
|
||||
// A value of 0 means that this maximum is not known.
|
||||
static constexpr uint16_t kMaxStream = 0;
|
||||
// Size of Wrapped_Key_IV (IV for decrypting the wrapped key) in bytes.
|
||||
@@ -44,6 +47,8 @@ static constexpr size_t kMaxAllowedAgeRestriction = 99;
|
||||
static constexpr size_t kEntitlementKeyIdSizeBytes = 16;
|
||||
// Size of entitlement key value in bytes.
|
||||
static constexpr size_t kEntitlementKeyValueSizeBytes = 32;
|
||||
// The default track type value fed to inner ECM generator.
|
||||
static constexpr char kDefaultTrackType[] = "track_type";
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -59,7 +64,8 @@ Status ProcessPrivateParameters(const char* const request, uint16_t param_type,
|
||||
*offset += param_length;
|
||||
break;
|
||||
case STREAM_TRACK_TYPE:
|
||||
params->stream_track_type = std::string(request + *offset, param_length);
|
||||
LOG(WARNING) << "Ignoring STREAM_TRACK_TYPE parameter of " << param_length
|
||||
<< " bytes long";
|
||||
*offset += param_length;
|
||||
break;
|
||||
case CONTENT_IV:
|
||||
@@ -98,6 +104,16 @@ Status ProcessPrivateParameters(const char* const request, uint16_t param_type,
|
||||
*offset += kEntitlementKeyValueSizeBytes;
|
||||
break;
|
||||
}
|
||||
case FINGERPRINTING_CONTROL:
|
||||
params->fingerprinting_control =
|
||||
std::string(request + *offset, param_length);
|
||||
*offset += param_length;
|
||||
break;
|
||||
case SERVICE_BLOCKING_GROUP:
|
||||
params->service_blocking_groups.push_back(
|
||||
std::string(request + *offset, param_length));
|
||||
*offset += param_length;
|
||||
break;
|
||||
default:
|
||||
return Status(
|
||||
error::UNIMPLEMENTED,
|
||||
@@ -132,7 +148,7 @@ Status HandleAccessCriteria(const char* const request, size_t request_length,
|
||||
|
||||
// Local helper function that processes all the params in an ECMG request.
|
||||
Status HandleParameters(const char* const request, size_t request_length,
|
||||
EcmgParameters* params) {
|
||||
EcmgParameters* params, bool process_access_criteria) {
|
||||
DCHECK(request);
|
||||
DCHECK(params);
|
||||
Status status;
|
||||
@@ -230,10 +246,14 @@ Status HandleParameters(const char* const request, size_t request_length,
|
||||
offset += param_length;
|
||||
break;
|
||||
case ACCESS_CRITERIA:
|
||||
if (process_access_criteria) {
|
||||
status = HandleAccessCriteria(request + offset, param_length, params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
} else {
|
||||
params->access_criteria = std::string(request + offset, param_length);
|
||||
}
|
||||
offset += param_length;
|
||||
break;
|
||||
case ERROR_STATUS:
|
||||
@@ -399,9 +419,8 @@ void BuildStreamCloseResponse(uint16_t channel_id, uint16_t stream_id,
|
||||
}
|
||||
|
||||
void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
|
||||
uint8_t* ecm_datagram, char* message,
|
||||
absl::Span<const uint8_t> ecm_datagram, char* message,
|
||||
size_t* message_length) {
|
||||
DCHECK(ecm_datagram);
|
||||
DCHECK(message);
|
||||
DCHECK(message_length);
|
||||
simulcrypt_util::BuildMessageHeader(
|
||||
@@ -412,16 +431,67 @@ void BuildEcmResponse(uint16_t channel_id, uint16_t stream_id, uint16_t cp_numbe
|
||||
message_length);
|
||||
simulcrypt_util::AddUint16Param(CP_NUMBER, cp_number, message,
|
||||
message_length);
|
||||
simulcrypt_util::AddParam(ECM_DATAGRAM, ecm_datagram, kTsPacketSize, message,
|
||||
message_length);
|
||||
simulcrypt_util::AddParam(ECM_DATAGRAM, ecm_datagram.data(),
|
||||
ecm_datagram.size(), message, message_length);
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
|
||||
Status CheckReceivedContentIv(const std::vector<std::string>& content_ivs,
|
||||
int expected_size) {
|
||||
if (content_ivs.size() > expected_size) {
|
||||
return {
|
||||
error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Too many content ivs, expecting ", expected_size, ".")};
|
||||
}
|
||||
if (content_ivs.size() < expected_size) {
|
||||
return {
|
||||
error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Not enough content ivs, expecting ", expected_size, ".")};
|
||||
}
|
||||
if (content_ivs[0].size() != 8 && content_ivs[0].size() != 16) {
|
||||
return {error::INVALID_ARGUMENT, "Wrong content iv size: must be 8 or 16."};
|
||||
}
|
||||
for (const auto& content_iv : content_ivs) {
|
||||
if (content_iv.size() != content_ivs[0].size()) {
|
||||
return {error::INVALID_ARGUMENT, "Size of content ivs must match."};
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
EcmgClientHandler::EcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
: channel_id_set_(false), custom_key_fetcher_(nullptr) {
|
||||
: channel_id_set_(false),
|
||||
custom_key_fetcher_(nullptr),
|
||||
custom_ac_processor_(nullptr) {
|
||||
DCHECK(ecmg_config);
|
||||
ecmg_config_ = ecmg_config;
|
||||
}
|
||||
@@ -448,7 +518,10 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
BigEndianToHost16(&request_length, request + offset);
|
||||
offset += MESSAGE_LENGTH_SIZE;
|
||||
EcmgParameters params;
|
||||
Status status = HandleParameters(request + offset, request_length, ¶ms);
|
||||
const bool should_sdk_process_access_criteria =
|
||||
custom_ac_processor_ == nullptr;
|
||||
Status status = HandleParameters(request + offset, request_length, ¶ms,
|
||||
should_sdk_process_access_criteria);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status),
|
||||
@@ -475,6 +548,9 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response,
|
||||
HandleStreamCloseRequest(params, response, response_length);
|
||||
break;
|
||||
case ECMG_CW_PROVISION:
|
||||
if (!should_sdk_process_access_criteria) {
|
||||
UpdateParamsWithCustomAccessCriteriaProcessor(params);
|
||||
}
|
||||
HandleCwProvision(params, response, response_length);
|
||||
break;
|
||||
case ECMG_CHANNEL_ERROR:
|
||||
@@ -503,10 +579,10 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params,
|
||||
}
|
||||
// The super_cas_id is a 32-bit identifier formed by the concatenation of the
|
||||
// CA_system_id (16 bit) and the CA_subsystem_id (16 bit).
|
||||
uint16_t cas_id = params.super_cas_id >> 16;
|
||||
if (cas_id != kWidevineSystemId &&
|
||||
(cas_id < kWidevineNewSystemIdLowerBound ||
|
||||
cas_id > kWidevineNewSystemIdUpperBound)) {
|
||||
cas_id_ = params.super_cas_id >> 16;
|
||||
if (cas_id_ != kWidevineSystemId &&
|
||||
(cas_id_ < kWidevineNewSystemIdLowerBound ||
|
||||
cas_id_ > kWidevineNewSystemIdUpperBound)) {
|
||||
BuildChannelError(params.ecm_channel_id, UNKNOWN_SUPER_CAS_ID_VALUE, "",
|
||||
response, response_length);
|
||||
return;
|
||||
@@ -596,8 +672,12 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
return;
|
||||
}
|
||||
|
||||
streams_info_[params.ecm_stream_id] = absl::make_unique<EcmgStreamInfo>();
|
||||
streams_info_[params.ecm_stream_id]->ecm_id = params.ecm_id;
|
||||
auto stream_info = absl::make_unique<EcmgStreamInfo>();
|
||||
stream_info->ecm_id = params.ecm_id;
|
||||
stream_info->crypto_mode = ecmg_config_->crypto_mode;
|
||||
stream_info->age_restriction = age_restriction_;
|
||||
stream_info->content_ivs.assign(content_ivs_.begin(), content_ivs_.end());
|
||||
streams_info_[params.ecm_stream_id] = std::move(stream_info);
|
||||
|
||||
Status status = UpdateStreamPrivateParameters(params);
|
||||
if (!status.ok()) {
|
||||
@@ -615,27 +695,10 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params,
|
||||
const std::vector<EntitlementKeyInfo>& fetched_entitlements =
|
||||
custom_key_fetcher_(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.ecm_id);
|
||||
// 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 {
|
||||
entitlements.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.
|
||||
entitlements.insert(fetched_entitlement.is_even_key
|
||||
? entitlements.begin()
|
||||
: entitlements.end(),
|
||||
entitlement_comb);
|
||||
}
|
||||
}
|
||||
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,
|
||||
@@ -715,6 +778,35 @@ void EcmgClientHandler::HandleStreamError(const EcmgParameters& params,
|
||||
*response_length = 0;
|
||||
}
|
||||
|
||||
void EcmgClientHandler::UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
EcmgParameters& params) const {
|
||||
DCHECK(custom_ac_processor_ != nullptr);
|
||||
const EcmgCustomParameters& custom_params = custom_ac_processor_(
|
||||
params.ecm_channel_id, params.ecm_stream_id, params.access_criteria);
|
||||
// Load results to |params|.
|
||||
if (custom_params.age_restriction >= 0) {
|
||||
params.age_restriction = custom_params.age_restriction;
|
||||
}
|
||||
if (!custom_params.crypto_mode.empty()) {
|
||||
params.crypto_mode = custom_params.crypto_mode;
|
||||
}
|
||||
if (!custom_params.content_ivs.empty()) {
|
||||
params.content_ivs = custom_params.content_ivs;
|
||||
}
|
||||
if (!custom_params.entitlement_keys.empty()) {
|
||||
const std::vector<EntitlementIdKeyComb>& converted_entitlements =
|
||||
ConvertEntitlementKeyInfo(custom_params.entitlement_keys);
|
||||
params.entitlement_comb.assign(converted_entitlements.begin(),
|
||||
converted_entitlements.end());
|
||||
}
|
||||
if (!custom_params.fingerprinting_control.empty()) {
|
||||
params.fingerprinting_control = custom_params.fingerprinting_control;
|
||||
}
|
||||
if (!custom_params.service_blocking_groups.empty()) {
|
||||
params.service_blocking_groups = custom_params.service_blocking_groups;
|
||||
}
|
||||
}
|
||||
|
||||
void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
char* response,
|
||||
size_t* response_length) {
|
||||
@@ -766,8 +858,9 @@ void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
}
|
||||
|
||||
// Build Ecm datagram.
|
||||
uint8_t ecm_datagram[kTsPacketSize];
|
||||
status = BuildEcmDatagram(params, ecm_datagram);
|
||||
uint8_t ecm_datagram[kMaxPossibleTsPacketsSizeBytes];
|
||||
ssize_t bytes_modified = 0;
|
||||
status = BuildEcmDatagram(params, ecm_datagram, &bytes_modified);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status.ToString();
|
||||
BuildStreamError(params.ecm_channel_id, params.ecm_stream_id,
|
||||
@@ -777,43 +870,18 @@ void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params,
|
||||
}
|
||||
|
||||
// Success. Send response message containing Ecm datagram.
|
||||
BuildEcmResponse(params.ecm_channel_id, params.ecm_stream_id,
|
||||
params.cp_number, ecm_datagram, response, response_length);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::UpdateCommonPrivateParameters(
|
||||
const EcmgParameters& params) {
|
||||
if (params.age_restriction != 0xff) {
|
||||
if (params.age_restriction > kMaxAllowedAgeRestriction) {
|
||||
return {error::INVALID_ARGUMENT, "Age restriction too large."};
|
||||
}
|
||||
age_restriction_ = params.age_restriction;
|
||||
}
|
||||
if (!params.content_ivs.empty()) {
|
||||
if (params.content_ivs.size() < ecmg_config_->number_of_content_keys) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Not enough content ivs, expecting ",
|
||||
ecmg_config_->number_of_content_keys, ".")};
|
||||
}
|
||||
if (params.content_ivs[0].size() != 8 &&
|
||||
params.content_ivs[0].size() != 16) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"Wrong content iv size: must be 8 or 16."};
|
||||
}
|
||||
for (size_t i = 1; i < ecmg_config_->number_of_content_keys; i++) {
|
||||
if (params.content_ivs[0].size() != params.content_ivs[i].size()) {
|
||||
return {error::INVALID_ARGUMENT, "Size of content ivs must match."};
|
||||
}
|
||||
}
|
||||
}
|
||||
return OkStatus();
|
||||
BuildEcmResponse(
|
||||
params.ecm_channel_id, params.ecm_stream_id, params.cp_number,
|
||||
absl::MakeSpan(ecm_datagram, bytes_modified), response, response_length);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::UpdateChannelPrivateParameters(
|
||||
const EcmgParameters& params) {
|
||||
Status status = UpdateCommonPrivateParameters(params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
if (params.age_restriction >= 0) {
|
||||
if (params.age_restriction > kMaxAllowedAgeRestriction) {
|
||||
return {error::INVALID_ARGUMENT, "Age restriction too large."};
|
||||
}
|
||||
age_restriction_ = params.age_restriction;
|
||||
}
|
||||
if (!params.crypto_mode.empty()) {
|
||||
if (!StringToCryptoMode(params.crypto_mode, &ecmg_config_->crypto_mode)) {
|
||||
@@ -822,6 +890,11 @@ Status EcmgClientHandler::UpdateChannelPrivateParameters(
|
||||
}
|
||||
}
|
||||
if (!params.content_ivs.empty()) {
|
||||
Status status = CheckReceivedContentIv(
|
||||
params.content_ivs, ecmg_config_->number_of_content_keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end());
|
||||
}
|
||||
return OkStatus();
|
||||
@@ -832,27 +905,57 @@ Status EcmgClientHandler::UpdateStreamPrivateParameters(
|
||||
DCHECK(streams_info_.contains(params.ecm_stream_id));
|
||||
EcmgStreamInfo* stream_info = streams_info_[params.ecm_stream_id].get();
|
||||
|
||||
Status status = UpdateCommonPrivateParameters(params);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
// Changing parameters used in Ecm::Initialize() needs to destroy the current
|
||||
// ECM generator held by the stream.
|
||||
bool invalidate_ecm_gen = false;
|
||||
|
||||
if (params.age_restriction >= 0) {
|
||||
if (params.age_restriction > kMaxAllowedAgeRestriction) {
|
||||
return {error::INVALID_ARGUMENT, "Age restriction too large."};
|
||||
}
|
||||
if (stream_info->age_restriction != params.age_restriction) {
|
||||
stream_info->age_restriction = params.age_restriction;
|
||||
invalidate_ecm_gen = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.crypto_mode.empty()) {
|
||||
if (!StringToCryptoMode(params.crypto_mode, &stream_info->crypto_mode)) {
|
||||
CryptoMode new_crypto_mode;
|
||||
if (!StringToCryptoMode(params.crypto_mode, &new_crypto_mode)) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")};
|
||||
}
|
||||
if (stream_info->crypto_mode != new_crypto_mode) {
|
||||
stream_info->crypto_mode = new_crypto_mode;
|
||||
invalidate_ecm_gen = true;
|
||||
}
|
||||
if (!params.stream_track_type.empty()) {
|
||||
stream_info->track_type = params.stream_track_type;
|
||||
}
|
||||
|
||||
if (!params.content_ivs.empty()) {
|
||||
Status status = CheckReceivedContentIv(
|
||||
params.content_ivs, ecmg_config_->number_of_content_keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (stream_info->content_ivs.empty() ||
|
||||
params.content_ivs[0].size() != stream_info->content_ivs[0].size()) {
|
||||
invalidate_ecm_gen = true;
|
||||
}
|
||||
stream_info->content_ivs.assign(params.content_ivs.begin(),
|
||||
params.content_ivs.end());
|
||||
}
|
||||
if (!params.entitlement_comb.empty()) {
|
||||
|
||||
if (!params.entitlement_comb.empty() &&
|
||||
stream_info->entitlement_comb != params.entitlement_comb) {
|
||||
stream_info->entitlement_comb.assign(params.entitlement_comb.begin(),
|
||||
params.entitlement_comb.end());
|
||||
invalidate_ecm_gen = true;
|
||||
}
|
||||
|
||||
if (invalidate_ecm_gen) {
|
||||
stream_info->ecm.reset();
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
@@ -861,7 +964,7 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get();
|
||||
DCHECK(stream_info->ecm == nullptr);
|
||||
|
||||
if (stream_info->content_ivs.empty() && content_ivs_.empty()) {
|
||||
if (stream_info->content_ivs.empty()) {
|
||||
return {error::NOT_FOUND, "Content iv not specified."};
|
||||
}
|
||||
if (stream_info->entitlement_comb.empty()) {
|
||||
@@ -877,35 +980,33 @@ Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) {
|
||||
bool key_rotation = ecmg_config_->number_of_content_keys > 1;
|
||||
EcmInitParameters ecm_init_params;
|
||||
ecm_init_params.key_rotation_enabled = key_rotation;
|
||||
ecm_init_params.age_restriction = age_restriction_;
|
||||
ecm_init_params.crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid
|
||||
? ecmg_config_->crypto_mode
|
||||
: stream_info->crypto_mode;
|
||||
ecm_init_params.content_iv_size = kIvSize8;
|
||||
if ((!stream_info->content_ivs.empty() &&
|
||||
stream_info->content_ivs[0].size() == 16) ||
|
||||
(!content_ivs_.empty() && content_ivs_[0].size() == 16)) {
|
||||
ecm_init_params.content_iv_size = kIvSize16;
|
||||
}
|
||||
ecm_init_params.age_restriction = stream_info->age_restriction;
|
||||
ecm_init_params.crypto_mode = stream_info->crypto_mode;
|
||||
ecm_init_params.content_iv_size =
|
||||
stream_info->content_ivs[0].size() == 16 ? kIvSize16 : kIvSize8;
|
||||
ecm_init_params.cas_id = cas_id_;
|
||||
ecm_init_params.ecm_version = ecmg_config_->ecm_version;
|
||||
ecm_init_params.ecc_private_signing_key =
|
||||
ecmg_config_->ecc_private_signing_key;
|
||||
|
||||
std::vector<EntitlementKeyInfo> entitlements;
|
||||
entitlements.reserve(stream_info->entitlement_comb.size());
|
||||
for (size_t i = 0; i < stream_info->entitlement_comb.size(); i++) {
|
||||
entitlements.emplace_back();
|
||||
EntitlementKeyInfo* entitlement = &entitlements.back();
|
||||
entitlement->track_type = stream_info->track_type;
|
||||
entitlement->track_type = kDefaultTrackType;
|
||||
entitlement->is_even_key = key_rotation ? i % 2 == 0 : true;
|
||||
entitlement->key_id = stream_info->entitlement_comb[i].key_id;
|
||||
entitlement->key_value = stream_info->entitlement_comb[i].key_value;
|
||||
}
|
||||
|
||||
stream_info->ecm = absl::make_unique<Ecm>();
|
||||
stream_info->ecm = CreateEcmInstance();
|
||||
return stream_info->ecm->Initialize(ecm_init_params, entitlements);
|
||||
}
|
||||
|
||||
Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) const {
|
||||
DCHECK(ecm_datagram);
|
||||
absl::Span<uint8_t> ecm_datagram,
|
||||
ssize_t* bytes_modified) const {
|
||||
DCHECK(streams_info_.contains(params.ecm_stream_id));
|
||||
EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get();
|
||||
DCHECK(stream_info->ecm);
|
||||
@@ -918,12 +1019,8 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
}
|
||||
|
||||
// Generate serialized ECM.
|
||||
CryptoMode crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid
|
||||
? ecmg_config_->crypto_mode
|
||||
: stream_info->crypto_mode;
|
||||
|
||||
// If two keys are present, the even key should be put first, followed by the
|
||||
// odd key.
|
||||
// If two keys are present, the even key should be put first, followed by
|
||||
// the odd key.
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
keys.reserve(key_count);
|
||||
for (const auto& cp_cw : params.cp_cw_combinations) {
|
||||
@@ -931,7 +1028,7 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
: keys.emplace(keys.end());
|
||||
key_info->key_value = cp_cw.cw;
|
||||
// Make content key to 16 bytes if crypto mode is Csa2.
|
||||
if (crypto_mode == CryptoMode::kDvbCsa2 &&
|
||||
if (stream_info->crypto_mode == CryptoMode::kDvbCsa2 &&
|
||||
key_info->key_value.size() == 8) {
|
||||
key_info->key_value =
|
||||
absl::StrCat(key_info->key_value, key_info->key_value);
|
||||
@@ -945,25 +1042,57 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
key_info->wrapped_key_iv = generated_key_iv.value();
|
||||
}
|
||||
|
||||
if (content_ivs_.size() < key_count &&
|
||||
stream_info->content_ivs.size() < key_count) {
|
||||
if (stream_info->content_ivs.size() < key_count) {
|
||||
return {error::INVALID_ARGUMENT, "Not enough content iv."};
|
||||
}
|
||||
// The first iv received is for even key and second is for odd key.
|
||||
for (size_t i = 0; i < key_count; ++i) {
|
||||
keys[i].content_iv = stream_info->content_ivs.size() >= key_count
|
||||
? stream_info->content_ivs[i]
|
||||
: content_ivs_[i];
|
||||
keys[i].content_iv = stream_info->content_ivs[i];
|
||||
}
|
||||
|
||||
EcmFingerprintingParams fingerprinting_params;
|
||||
if (fingerprinting_func_ != nullptr) {
|
||||
fingerprinting_params =
|
||||
fingerprinting_func_(params.ecm_channel_id, params.ecm_stream_id);
|
||||
}
|
||||
// Fingerprinting info received from SCS has higher priority.
|
||||
if (!params.fingerprinting_control.empty()) {
|
||||
if (!fingerprinting_params.control.empty()) {
|
||||
LOG(WARNING) << "Received fingerprinting info from both SCS and callback "
|
||||
"function. Using the one received from SCS.";
|
||||
}
|
||||
fingerprinting_params.control = params.fingerprinting_control;
|
||||
}
|
||||
stream_info->ecm->SetFingerprinting(
|
||||
fingerprinting_params.control.empty() ? nullptr : &fingerprinting_params);
|
||||
|
||||
EcmServiceBlockingParams service_blocking_params;
|
||||
if (service_blocking_func_ != nullptr) {
|
||||
service_blocking_params =
|
||||
service_blocking_func_(params.ecm_channel_id, params.ecm_stream_id);
|
||||
}
|
||||
// Service blocking info received from SCS has higher priority.
|
||||
if (!params.service_blocking_groups.empty()) {
|
||||
if (!service_blocking_params.device_groups.empty()) {
|
||||
LOG(WARNING) << "Received service blocking info from both SCS and "
|
||||
"callback function. Using the one received from SCS.";
|
||||
}
|
||||
service_blocking_params.device_groups.assign(
|
||||
params.service_blocking_groups.begin(),
|
||||
params.service_blocking_groups.end());
|
||||
}
|
||||
stream_info->ecm->SetServiceBlocking(
|
||||
service_blocking_params.device_groups.empty() ? nullptr
|
||||
: &service_blocking_params);
|
||||
|
||||
Status status;
|
||||
std::string serialized_ecm;
|
||||
if (key_count > 1) {
|
||||
status = stream_info->ecm->GenerateEcm(
|
||||
&keys[0], &keys[1], stream_info->track_type, &serialized_ecm);
|
||||
status = stream_info->ecm->GenerateEcm(&keys[0], &keys[1],
|
||||
kDefaultTrackType, &serialized_ecm);
|
||||
} else {
|
||||
status = stream_info->ecm->GenerateSingleKeyEcm(
|
||||
&keys[0], stream_info->track_type, &serialized_ecm);
|
||||
status = stream_info->ecm->GenerateSingleKeyEcm(&keys[0], kDefaultTrackType,
|
||||
&serialized_ecm);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -978,7 +1107,7 @@ Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params,
|
||||
status = Ecm::GenerateTsPacket(
|
||||
serialized_ecm, pid,
|
||||
params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81,
|
||||
&continuity_counter, ecm_datagram);
|
||||
&continuity_counter, ecm_datagram, bytes_modified);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << status;
|
||||
return {error::INTERNAL, "GenerateTsPacket failed."};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <cstdint>
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/public/wv_cas_types.h"
|
||||
@@ -34,6 +35,13 @@ struct EcmgCpCwCombination {
|
||||
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.
|
||||
@@ -50,22 +58,24 @@ struct EcmgParameters {
|
||||
std::vector<std::string> error_information;
|
||||
|
||||
// User defined paremeters below.
|
||||
uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value.
|
||||
int age_restriction = -1; // Negative value to indidate the field is not set.
|
||||
std::string crypto_mode;
|
||||
std::string stream_track_type;
|
||||
std::vector<std::string> content_ivs; // 8 or 16 bytes, one for each key.
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb;
|
||||
std::string access_criteria;
|
||||
std::string fingerprinting_control;
|
||||
std::vector<std::string> service_blocking_groups;
|
||||
};
|
||||
|
||||
struct EcmgStreamInfo {
|
||||
uint16_t ecm_id;
|
||||
std::string track_type;
|
||||
// Will use |ecmg_config_|.crypto_mode if invalid.
|
||||
CryptoMode crypto_mode = CryptoMode::kInvalid;
|
||||
// 8 or 16 bytes, one for each key. Will use |content_ivs_| if empty.
|
||||
CryptoMode crypto_mode;
|
||||
// 8 or 16 bytes, one for each key.
|
||||
std::vector<std::string> content_ivs;
|
||||
std::vector<EntitlementIdKeyComb> entitlement_comb;
|
||||
std::unique_ptr<Ecm> ecm;
|
||||
uint8_t age_restriction;
|
||||
};
|
||||
|
||||
// A class that handles one (and only one) ECMG client.
|
||||
@@ -92,6 +102,30 @@ class EcmgClientHandler {
|
||||
custom_key_fetcher_ = fetcher;
|
||||
}
|
||||
|
||||
// Sets the custom access criteria processing function used by ECMG. If
|
||||
// set, the function will be called when an access criteria is received from
|
||||
// SCS.
|
||||
virtual void SetCustomAccessCriteriaProcessFunc(
|
||||
CustomAccessCriteriaProcessFunc ac_processor) {
|
||||
custom_ac_processor_ = ac_processor;
|
||||
}
|
||||
|
||||
// Sets the fingerprinting setting function, which will be called upon each
|
||||
// CwProvisioning request. It controls the fingerprinting information carried
|
||||
// in ECMs.
|
||||
virtual void SetFingerprintingSettingFunc(
|
||||
FingerprintingSettingFunc fingerprinting_func) {
|
||||
fingerprinting_func_ = fingerprinting_func;
|
||||
}
|
||||
|
||||
// Sets the service blocking setting function, which will be called upon each
|
||||
// CwProvisioning request. It controls the service blocking information
|
||||
// carried in ECMs.
|
||||
virtual void SetServiceBlockingSettingFunc(
|
||||
ServiceBlockingSettingFunc service_blocking_func) {
|
||||
service_blocking_func_ = service_blocking_func;
|
||||
}
|
||||
|
||||
private:
|
||||
void HandleChannelSetup(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
@@ -111,11 +145,16 @@ class EcmgClientHandler {
|
||||
size_t* response_length) const;
|
||||
void HandleCwProvision(const EcmgParameters& params, char* response,
|
||||
size_t* response_length);
|
||||
void UpdateParamsWithCustomAccessCriteriaProcessor(
|
||||
EcmgParameters& params) const;
|
||||
|
||||
// Update private parameters using |params|.
|
||||
// Update channel level private parameters using |params|.
|
||||
Status UpdateChannelPrivateParameters(const EcmgParameters& params);
|
||||
// Update stream level private parameters using |params|. If changes in
|
||||
// |params| makes the stream hold Ecm instance (EcmgStreamInfo.ecm) no longer
|
||||
// usable (changing params used in Ecm::Initialize()), it will invalidate
|
||||
// (destroy) it.
|
||||
Status UpdateStreamPrivateParameters(const EcmgParameters& params);
|
||||
Status UpdateCommonPrivateParameters(const EcmgParameters& params);
|
||||
|
||||
// Check if all required parameters have been set. If so, initialize |ecm_| by
|
||||
// fetching entitlement keys.
|
||||
@@ -123,16 +162,22 @@ class EcmgClientHandler {
|
||||
// Gather all information needed to build a TS packet |ecm_datagram|
|
||||
// conatianing an ECM.
|
||||
Status BuildEcmDatagram(const EcmgParameters& params,
|
||||
uint8_t* ecm_datagram) const;
|
||||
absl::Span<uint8_t> ecm_datagram,
|
||||
ssize_t* bytes_modified) const;
|
||||
|
||||
// Generates a random wrapped key iv string. Returns true on success, false
|
||||
// otherwise. The main purpose for this function is easier testing.
|
||||
virtual absl::optional<std::string> GenerateRandomWrappedKeyIv() const;
|
||||
// For easier testing.
|
||||
virtual std::unique_ptr<Ecm> CreateEcmInstance() const {
|
||||
return absl::make_unique<Ecm>();
|
||||
}
|
||||
|
||||
EcmgConfig* ecmg_config_;
|
||||
// Per spec, "There is always one (and only one) channel per TCP connection".
|
||||
bool channel_id_set_ = false;
|
||||
uint16_t channel_id_;
|
||||
uint16_t cas_id_;
|
||||
|
||||
// Channel specific information.
|
||||
uint8_t age_restriction_ = 0;
|
||||
@@ -142,6 +187,12 @@ class EcmgClientHandler {
|
||||
absl::flat_hash_map<uint16_t, std::unique_ptr<EcmgStreamInfo>> streams_info_;
|
||||
// Used to fetch entitlement keys if none are received from SCS.
|
||||
EntitlementKeyFetcherFunc custom_key_fetcher_;
|
||||
// Used to process custom aceess criteria received in CwProvision message.
|
||||
CustomAccessCriteriaProcessFunc custom_ac_processor_;
|
||||
// Used to get fingerprinting information that will be embedded in ECMs.
|
||||
FingerprintingSettingFunc fingerprinting_func_;
|
||||
// Used to get service blocking information that will be embedded in ECMs.
|
||||
ServiceBlockingSettingFunc service_blocking_func_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "example/test_ecmg_messages.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
#include "media_cas_packager_sdk/internal/simulcrypt_util.h"
|
||||
@@ -35,7 +36,9 @@ using simulcrypt_util::AddUint16Param;
|
||||
using simulcrypt_util::AddUint32Param;
|
||||
using simulcrypt_util::AddUint8Param;
|
||||
using simulcrypt_util::BuildMessageHeader;
|
||||
using ::testing::ByMove;
|
||||
using ::testing::ElementsAreArray;
|
||||
using ::testing::Return;
|
||||
|
||||
constexpr size_t kBufferSize = 1024;
|
||||
constexpr uint16_t kWidevineSystemId = 0x4AD4;
|
||||
@@ -61,8 +64,11 @@ constexpr size_t kAgeRestriction = 3;
|
||||
constexpr char kCryptoMode[] = "AesScte";
|
||||
constexpr char kCryptoModeCsa2[] = "DvbCsa2";
|
||||
constexpr char kTrackTypesSD[] = "SD";
|
||||
constexpr char kTrackTypesHD[] = "HD";
|
||||
constexpr absl::string_view kWrappedKeyIv = "0123456701234567";
|
||||
constexpr int kExpectedEcmResponseLength = sizeof(kTestEcmgEcmResponse);
|
||||
constexpr char kFingerprintingControl[] = "control";
|
||||
constexpr char kServiceBlockingGroup1[] = "group1";
|
||||
constexpr char kServiceBlockingGroup2[] = "group2";
|
||||
|
||||
std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/, uint16_t /*ecm_id*/) {
|
||||
@@ -72,6 +78,21 @@ std::vector<EntitlementKeyInfo> FakeEntitlementKeyFetcherFunc(
|
||||
kEntitlementKeyValueEven}};
|
||||
}
|
||||
|
||||
EcmgCustomParameters FakeCustomAcProcessorFunc(
|
||||
uint16_t /*channel_id*/, uint16_t /*stream_id*/,
|
||||
const std::string& access_criteria) {
|
||||
EcmgCustomParameters params;
|
||||
params.age_restriction = access_criteria.empty() ? 0 : 3;
|
||||
params.content_ivs = {kContentKeyIvEven, kContentKeyIvOdd};
|
||||
params.crypto_mode = kCryptoMode;
|
||||
params.entitlement_keys = {{kTrackTypesSD, /*is_even_key*/ false,
|
||||
kEntitlementKeyIdOdd, kEntitlementKeyValueOdd},
|
||||
{kTrackTypesSD, /*is_even_key*/ true,
|
||||
kEntitlementKeyIdEven, kEntitlementKeyValueEven}};
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
class MockEcmgClientHandler : public EcmgClientHandler {
|
||||
public:
|
||||
explicit MockEcmgClientHandler(EcmgConfig* ecmg_config)
|
||||
@@ -79,11 +100,15 @@ class MockEcmgClientHandler : public EcmgClientHandler {
|
||||
absl::optional<std::string> GenerateRandomWrappedKeyIv() const override {
|
||||
return std::string(kWrappedKeyIv);
|
||||
}
|
||||
|
||||
MOCK_METHOD(std::unique_ptr<Ecm>, CreateEcmInstance, (), (const, override));
|
||||
};
|
||||
|
||||
class EcmgClientHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
EcmgClientHandlerTest() {
|
||||
EcmgClientHandlerTest() {}
|
||||
|
||||
void SetUp() override {
|
||||
config_.delay_start = 200;
|
||||
config_.delay_stop = 200;
|
||||
config_.ecm_rep_period = 100;
|
||||
@@ -91,6 +116,9 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
config_.access_criteria_transfer_mode = 1;
|
||||
config_.number_of_content_keys = 2;
|
||||
handler_ = absl::make_unique<MockEcmgClientHandler>(&config_);
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance).WillRepeatedly([] {
|
||||
return absl::make_unique<Ecm>();
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -107,19 +135,32 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void SetupValidChannel() {
|
||||
void SetupValidChannelWithPrivateParameters() {
|
||||
handler_->HandleRequest(kTestEcmgChannelSetupWithPrivateParameters,
|
||||
response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgChannelStatus));
|
||||
}
|
||||
|
||||
void SetupValidChannelStream() {
|
||||
SetupValidChannel();
|
||||
void SetupValidChannelStreamWithPrivateParameters() {
|
||||
SetupValidChannelWithPrivateParameters();
|
||||
handler_->HandleRequest(kTestEcmgStreamSetupWithPrivateParameters,
|
||||
response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgStreamStatus));
|
||||
}
|
||||
|
||||
void SetupValidStandardChannel() {
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgChannelStatus));
|
||||
}
|
||||
|
||||
void SetupValidStandardChannelStream() {
|
||||
SetupValidStandardChannel();
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgStreamStatus));
|
||||
}
|
||||
|
||||
void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id,
|
||||
@@ -146,7 +187,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
|
||||
void BuildStreamSetupRequest(uint16_t channel_id, uint16_t stream_id,
|
||||
uint16_t ecm_id, uint16_t nominal_CP_duration,
|
||||
const std::string& stream_track_type,
|
||||
const std::vector<std::string>& entitlements,
|
||||
const std::vector<std::string>& content_ivs,
|
||||
char* message, size_t* message_length) {
|
||||
@@ -160,11 +200,6 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message,
|
||||
message_length);
|
||||
|
||||
if (!stream_track_type.empty()) {
|
||||
AddParam(STREAM_TRACK_TYPE,
|
||||
reinterpret_cast<const uint8_t*>(stream_track_type.c_str()),
|
||||
stream_track_type.size(), message, message_length);
|
||||
}
|
||||
if (!entitlements.empty()) {
|
||||
for (const auto& entitlement : entitlements) {
|
||||
AddParam(ENTITLEMENT_ID_KEY_COMBINATION,
|
||||
@@ -185,7 +220,9 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
|
||||
void BuildCwProvisionRequest(
|
||||
uint16_t channel_id, uint16_t stream_id, uint16_t cp_number,
|
||||
const std::vector<EcmgCpCwCombination>& cp_cw_combination, char* message,
|
||||
const std::vector<EcmgCpCwCombination>& cp_cw_combination,
|
||||
const std::string& fingerprinting,
|
||||
const std::vector<std::string>& service_blockings, char* message,
|
||||
size_t* message_length) {
|
||||
EXPECT_TRUE(message != nullptr);
|
||||
EXPECT_TRUE(message_length != nullptr);
|
||||
@@ -201,6 +238,17 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message,
|
||||
message_length);
|
||||
}
|
||||
if (!fingerprinting.empty()) {
|
||||
AddParam(FINGERPRINTING_CONTROL,
|
||||
reinterpret_cast<const uint8_t*>(fingerprinting.c_str()),
|
||||
fingerprinting.size(), message, message_length);
|
||||
}
|
||||
for (auto const& sb : service_blockings) {
|
||||
AddParam(SERVICE_BLOCKING_GROUP,
|
||||
reinterpret_cast<const uint8_t*>(sb.c_str()), sb.size(), message,
|
||||
message_length);
|
||||
}
|
||||
|
||||
uint16_t total_param_length = *message_length - 5;
|
||||
Host16ToBigEndian(message + 3, &total_param_length);
|
||||
}
|
||||
@@ -230,7 +278,7 @@ class EcmgClientHandlerTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
EcmgConfig config_;
|
||||
std::unique_ptr<EcmgClientHandler> handler_;
|
||||
std::unique_ptr<MockEcmgClientHandler> handler_;
|
||||
char response_[kBufferSize];
|
||||
size_t response_len_ = 0;
|
||||
char request_[kBufferSize];
|
||||
@@ -323,7 +371,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
@@ -332,7 +380,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration, kTrackTypesHD,
|
||||
kChannelId, kStreamId + 1, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
@@ -342,13 +390,15 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) {
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27));
|
||||
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId + 1, kCpNumber,
|
||||
cp_cw_combination, request_, &request_len_);
|
||||
cp_cw_combination, /*fingerprinting=*/"",
|
||||
/*service_blockings=*/{}, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
}
|
||||
@@ -363,9 +413,9 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||
ElementsAreArray(kTestEcmgChannelStatus));
|
||||
|
||||
// No entitlement keys are injected.
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
/*entitlements=*/{}, {kContentKeyIvEven, kContentKeyIvOdd}, request_,
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
/*entitlements=*/{},
|
||||
{kContentKeyIvEven, kContentKeyIvOdd}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||
@@ -373,6 +423,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
EXPECT_THAT(absl::MakeSpan(response_, response_len_),
|
||||
ElementsAreArray(kTestEcmgStreamStatus));
|
||||
@@ -380,6 +431,46 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceFetchedEntitlements) {
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest,
|
||||
SuccessSequenceWithCustomAccessCriteriaProcessor) {
|
||||
handler_->SetCustomAccessCriteriaProcessFunc(FakeCustomAcProcessorFunc);
|
||||
SetupValidStandardChannelStream();
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
|
||||
EXPECT_EQ(response_len_, kExpectedEcmResponseLength);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SameParametersReuseEcm) {
|
||||
handler_->SetCustomAccessCriteriaProcessFunc(FakeCustomAcProcessorFunc);
|
||||
SetupValidStandardChannelStream();
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance)
|
||||
.Times(1)
|
||||
.WillOnce(Return(ByMove(absl::make_unique<Ecm>())));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, kExpectedEcmResponseLength);
|
||||
// Calling again will not invoke new Ecm.
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, kExpectedEcmResponseLength);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, ChangedParametersReinitializeEcm) {
|
||||
handler_->SetCustomAccessCriteriaProcessFunc(FakeCustomAcProcessorFunc);
|
||||
SetupValidStandardChannelStream();
|
||||
EXPECT_CALL(*handler_.get(), CreateEcmInstance).Times(2).WillRepeatedly([] {
|
||||
return absl::make_unique<Ecm>();
|
||||
});
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, kExpectedEcmResponseLength);
|
||||
// The fake FakeCustomAcProcessorFunc will set age restriction as the length
|
||||
// of access criteria, causing parameter change.
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
EXPECT_EQ(response_len_, kExpectedEcmResponseLength);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction,
|
||||
kCryptoModeCsa2, request_, &request_len_);
|
||||
@@ -388,7 +479,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
@@ -400,6 +491,7 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
{kCpNumber, kContentKeyEven8Bytes},
|
||||
{kCpNumber + 1, kContentKeyOdd8Bytes}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_);
|
||||
@@ -407,13 +499,13 @@ TEST_F(EcmgClientHandlerTest, SuccessSequenceCsa2) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessChannelError) {
|
||||
SetupValidChannel();
|
||||
SetupValidChannelWithPrivateParameters();
|
||||
handler_->HandleRequest(kTestEcmgChannelError, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SuccessStreamError) {
|
||||
SetupValidChannelStream();
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
handler_->HandleRequest(kTestEcmgStreamError, response_, &response_len_);
|
||||
EXPECT_EQ(0, response_len_);
|
||||
}
|
||||
@@ -445,7 +537,7 @@ TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleChannel) {
|
||||
SetupValidChannel();
|
||||
SetupValidChannelWithPrivateParameters();
|
||||
|
||||
// Setup channels muiltiple times (error).
|
||||
handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_);
|
||||
@@ -453,7 +545,7 @@ TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleChannel) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidStream) {
|
||||
SetupValidChannel();
|
||||
SetupValidChannelWithPrivateParameters();
|
||||
|
||||
// Cw provision without a valid stream (error).
|
||||
handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_);
|
||||
@@ -469,7 +561,7 @@ TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidStream) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleSameIdStream) {
|
||||
SetupValidChannelStream();
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
// Setup streams with the same stream_id muiltiple times (error).
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
CheckStreamError(ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response_,
|
||||
@@ -477,7 +569,7 @@ TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleSameIdStream) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
|
||||
SetupValidChannelStream();
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
// Cw provision with only one key while expecting two (error).
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionSingleKey, response_,
|
||||
&response_len_);
|
||||
@@ -487,6 +579,7 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
|
||||
// Cw provision with no key while expecting two (error).
|
||||
std::vector<EcmgCpCwCombination> cp_cw_combination = {};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response_,
|
||||
@@ -494,12 +587,13 @@ TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongContentKeySize) {
|
||||
SetupValidChannelStream();
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
// Content keys should be 16 byes.
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven8Bytes},
|
||||
{kCpNumber + 1, kContentKeyOdd8Bytes}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(INVALID_VALUE_FOR_DVB_PARAMETER, response_, response_len_);
|
||||
@@ -514,10 +608,10 @@ TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) {
|
||||
SetupValidChannel();
|
||||
SetupValidChannelWithPrivateParameters();
|
||||
// Setup a stream with an unexpected channel id (expecting kChannelId).
|
||||
BuildStreamSetupRequest(
|
||||
0, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
0, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
@@ -547,9 +641,9 @@ TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
|
||||
EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_);
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
/*entitlements*/ {}, {kContentKeyEven, kContentKeyEven}, request_,
|
||||
BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
/*entitlements*/ {},
|
||||
{kContentKeyEven, kContentKeyEven}, request_,
|
||||
&request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_);
|
||||
@@ -558,6 +652,7 @@ TEST_F(EcmgClientHandlerTest, NoInjectedEntitlements) {
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
|
||||
@@ -571,7 +666,7 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
|
||||
{kContentKeyEven, kContentKeyEven}, request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
@@ -581,6 +676,7 @@ TEST_F(EcmgClientHandlerTest, NotEnoughInjectedEntitlements) {
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
|
||||
@@ -594,7 +690,7 @@ TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
|
||||
EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_));
|
||||
|
||||
BuildStreamSetupRequest(
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration, kTrackTypesSD,
|
||||
kChannelId, kStreamId, kEcmId, kNominalCpDuration,
|
||||
{absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven),
|
||||
absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd),
|
||||
absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven)},
|
||||
@@ -606,6 +702,7 @@ TEST_F(EcmgClientHandlerTest, TooManyInjectedEntitlements) {
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
CheckStreamError(MISSING_MANDATORY_DVB_PARAMETER, response_, response_len_);
|
||||
@@ -619,11 +716,12 @@ TEST_F(EcmgClientHandlerTest, WrongMessageLength) {
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, BuildEcmDatagramSequenceOfEvenOdd) {
|
||||
SetupValidChannelStream();
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
|
||||
std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
EXPECT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||
@@ -633,6 +731,7 @@ TEST_F(EcmgClientHandlerTest, BuildEcmDatagramSequenceOfEvenOdd) {
|
||||
cp_cw_combination = {{kCpNumber + 1, kContentKeyOdd},
|
||||
{kCpNumber, kContentKeyEven}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
// Sequence of cp_cw_combination does not matter as even/odd is based on cp
|
||||
@@ -643,12 +742,34 @@ TEST_F(EcmgClientHandlerTest, BuildEcmDatagramSequenceOfEvenOdd) {
|
||||
cp_cw_combination = {{kCpNumber, kContentKeyOdd},
|
||||
{kCpNumber + 1, kContentKeyEven}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"", /*service_blockings=*/{},
|
||||
request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
// Swapping key value changes generated ecm.
|
||||
EXPECT_NE(std::string(response_, response_len_), first_response);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, NewCasIdEmbeddedInEcmSuccess) {
|
||||
uint16_t expected_cas_id = kWidevineNewSystemIdLowerBound + 1;
|
||||
// Set up test channel.
|
||||
BuildChannelSetupRequest(kChannelId, /*super_cas_id=*/expected_cas_id << 16,
|
||||
/*age_restriction=*/0,
|
||||
/*crypto_mode=*/"", request_, &request_len_);
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgChannelStatus));
|
||||
// Set up test stream.
|
||||
handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_);
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgStreamStatus));
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
|
||||
ASSERT_EQ(response_len_, sizeof(kTestEcmgEcmResponse));
|
||||
uint16_t actual_cas_id = (static_cast<uint8_t>(response_[35]) << 8) |
|
||||
static_cast<uint8_t>(response_[36]);
|
||||
EXPECT_EQ(actual_cas_id, expected_cas_id);
|
||||
}
|
||||
|
||||
class CasIdTest
|
||||
: public EcmgClientHandlerTest,
|
||||
public ::testing::WithParamInterface<::testing::tuple<int, bool>> {};
|
||||
@@ -681,6 +802,99 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
kWidevineNewSystemIdUpperBound + 1),
|
||||
::testing::Values(false)));
|
||||
|
||||
class MockEcm : public Ecm {
|
||||
public:
|
||||
MockEcm() = default;
|
||||
~MockEcm() override = default;
|
||||
MOCK_METHOD(void, SetFingerprinting, (const EcmFingerprintingParams*),
|
||||
(override));
|
||||
MOCK_METHOD(void, SetServiceBlocking, (const EcmServiceBlockingParams*),
|
||||
(override));
|
||||
};
|
||||
|
||||
EcmFingerprintingParams FakeFingerprintingSettingFunc(uint16_t channel_id,
|
||||
uint16_t stream_id) {
|
||||
return {kFingerprintingControl};
|
||||
}
|
||||
|
||||
MATCHER(CorrectFingerprinting, "") {
|
||||
return arg->control == kFingerprintingControl;
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SetFingerprintingSuccess) {
|
||||
handler_->SetFingerprintingSettingFunc(FakeFingerprintingSettingFunc);
|
||||
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, SetFingerprinting(CorrectFingerprinting())).Times(1);
|
||||
SetupValidStandardChannelStream();
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SetFingerprintingViaAceessCriteriaSuccess) {
|
||||
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, SetFingerprinting(CorrectFingerprinting())).Times(1);
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
kFingerprintingControl,
|
||||
/*service_blockings=*/{}, request_, &request_len_);
|
||||
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
}
|
||||
|
||||
EcmServiceBlockingParams FakeServiceBlockingSettingFunc(uint16_t channel_id,
|
||||
uint16_t stream_id) {
|
||||
return {{kServiceBlockingGroup1, kServiceBlockingGroup2}};
|
||||
}
|
||||
|
||||
MATCHER(CorrectServiceBlocking, "") {
|
||||
const std::vector<std::string> expected = {kServiceBlockingGroup1,
|
||||
kServiceBlockingGroup2};
|
||||
return arg->device_groups == expected;
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SetServiceBlockingSuccess) {
|
||||
handler_->SetServiceBlockingSettingFunc(FakeServiceBlockingSettingFunc);
|
||||
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, SetServiceBlocking(CorrectServiceBlocking())).Times(1);
|
||||
SetupValidStandardChannelStream();
|
||||
|
||||
handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_,
|
||||
&response_len_);
|
||||
}
|
||||
|
||||
TEST_F(EcmgClientHandlerTest, SetServiceBlockingViaAceessCriteriaSuccess) {
|
||||
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, SetServiceBlocking(CorrectServiceBlocking())).Times(1);
|
||||
SetupValidChannelStreamWithPrivateParameters();
|
||||
const std::vector<EcmgCpCwCombination> cp_cw_combination = {
|
||||
{kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}};
|
||||
BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination,
|
||||
/*fingerprinting=*/"",
|
||||
{kServiceBlockingGroup1, kServiceBlockingGroup2},
|
||||
request_, &request_len_);
|
||||
|
||||
handler_->HandleRequest(request_, response_, &response_len_);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
#define STREAM_TRACK_TYPE (0x8002)
|
||||
#define CONTENT_IV (0x8003)
|
||||
#define ENTITLEMENT_ID_KEY_COMBINATION (0x8004)
|
||||
#define FINGERPRINTING_CONTROL (0x8005)
|
||||
#define SERVICE_BLOCKING_GROUP (0x8006)
|
||||
|
||||
// ECMG protocol error values.
|
||||
#define INVALID_MESSAGE (0x0001)
|
||||
|
||||
@@ -122,11 +122,11 @@ Status Emm::GenerateEmm(std::string* serialized_emm) const {
|
||||
}
|
||||
|
||||
Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
|
||||
uint8_t* packet, ssize_t* packet_size) const {
|
||||
if (continuity_counter == nullptr || packet == nullptr ||
|
||||
packet_size == nullptr) {
|
||||
const absl::Span<uint8_t> packet,
|
||||
ssize_t* bytes_modified) const {
|
||||
if (continuity_counter == nullptr || bytes_modified == nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"continuity_counter, packet and packet_size must not be null"};
|
||||
"continuity_counter and bytes_modified must not be null"};
|
||||
}
|
||||
|
||||
std::string serialized_emm;
|
||||
@@ -134,25 +134,22 @@ Status Emm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (*packet_size < static_cast<ssize_t>(CalculateTsBufferSizeForEcmEmm(
|
||||
serialized_emm.size()))) {
|
||||
return {error::INVALID_ARGUMENT, "packet buffer is too small"};
|
||||
|
||||
size_t buf_size_needed =
|
||||
CalculateTsBufferSizeForEcmEmm(serialized_emm.size());
|
||||
if (packet.size() < buf_size_needed) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Packet buffer is too small. Minimum required: ",
|
||||
buf_size_needed, " bytes.")};
|
||||
}
|
||||
|
||||
ssize_t bytes_modified = 0;
|
||||
status = InsertEmmAsTsPacket(serialized_emm, pid, continuity_counter, packet,
|
||||
&bytes_modified);
|
||||
if (!status.ok() || bytes_modified <= 0 ||
|
||||
bytes_modified % kTsPacketSize != 0) {
|
||||
LOG(ERROR) << "Failed to generate TS packet: "
|
||||
<< (status.ok()
|
||||
? absl::StrCat("Unexpected bytes_modified value ",
|
||||
bytes_modified)
|
||||
: status.error_message());
|
||||
return {error::INTERNAL, "Failed to generate TS packet"};
|
||||
}
|
||||
status = InsertEmmAsTsPacket(serialized_emm, pid, continuity_counter,
|
||||
packet.subspan(0, buf_size_needed));
|
||||
|
||||
*packet_size = bytes_modified;
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
*bytes_modified += buf_size_needed;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
@@ -67,11 +68,11 @@ class Emm {
|
||||
// - |packet| a buffer to be used to return the generated TS packet. Must be
|
||||
// able to hold the maximum possible size of genereated Ts packets
|
||||
// (kMaxPossibleTsPacketsSizeBytes = 1128 bytes).
|
||||
// - |packet_size| is the size of the allocated |packet|. It will be updated
|
||||
// as the number of bytes actually used.
|
||||
// - |bytes_modified| will be incremented by the used buffer size if insertion
|
||||
// of ECM into |buffer| is successful.
|
||||
virtual Status GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
|
||||
uint8_t* packet,
|
||||
ssize_t* packet_size) const;
|
||||
const absl::Span<uint8_t> packet,
|
||||
ssize_t* bytes_modified) const;
|
||||
|
||||
private:
|
||||
struct EmmSerializingParameters {
|
||||
|
||||
@@ -376,11 +376,11 @@ TEST_F(EmmTest, GenerateEmmNoPayloadSuccess) {
|
||||
TEST_F(EmmTest, GenerateEmmTsPacketsNoPayloadSuccess) {
|
||||
uint8_t counter = 0;
|
||||
uint8_t packet[kTsPacketSize];
|
||||
ssize_t packet_size = kTsPacketSize;
|
||||
ssize_t bytes_modified = 0;
|
||||
EXPECT_OK(
|
||||
emm_->GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size));
|
||||
emm_->GenerateEmmTsPackets(kTestPid, &counter, packet, &bytes_modified));
|
||||
EXPECT_THAT(packet, ElementsAreArray(kExpectedEmptyEmmPacket));
|
||||
EXPECT_EQ(packet_size, kTsPacketSize);
|
||||
EXPECT_EQ(bytes_modified, kTsPacketSize);
|
||||
}
|
||||
|
||||
// Verifies GenerateSignature is successful with empty payload.
|
||||
@@ -450,35 +450,20 @@ TEST(GenerateEmmTsPacketsTest, MockPayloadSuccess) {
|
||||
|
||||
uint8_t counter = 0;
|
||||
uint8_t packet[kTsPacketSize];
|
||||
ssize_t packet_size = kTsPacketSize;
|
||||
EXPECT_OK(
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size));
|
||||
ssize_t bytes_modified = 0;
|
||||
EXPECT_OK(emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet,
|
||||
&bytes_modified));
|
||||
EXPECT_THAT(packet, ElementsAreArray(kExpectedMockEmmPacket));
|
||||
EXPECT_EQ(packet_size, kTsPacketSize);
|
||||
EXPECT_EQ(bytes_modified, kTsPacketSize);
|
||||
}
|
||||
|
||||
// Verifies GenerateEmmTsPackets fails if the given continuity counter is null.
|
||||
TEST(GenerateEmmTsPacketsTest, NullContinuityCounterPointerFail) {
|
||||
Emm emm_gen;
|
||||
uint8_t packet[kTsPacketSize];
|
||||
ssize_t packet_size = kTsPacketSize;
|
||||
ssize_t bytes_modified = 0;
|
||||
EXPECT_EQ(
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, nullptr, packet, &packet_size)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
// Verifies GenerateEmmTsPackets fails if the given packet buffer is null.
|
||||
TEST(GenerateEmmTsPacketsTest, NullPacketBufferFail) {
|
||||
Emm emm_gen;
|
||||
uint8_t counter = 0;
|
||||
uint8_t packet[kTsPacketSize];
|
||||
ssize_t packet_size = kTsPacketSize;
|
||||
EXPECT_EQ(
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, nullptr, &packet_size)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
EXPECT_EQ(emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, nullptr)
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, nullptr, packet, &bytes_modified)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
@@ -491,10 +476,10 @@ TEST(GenerateEmmTsPacketsTest, PacketBufferTooSmallFail) {
|
||||
.WillOnce(DoAll(SetArgPointee<0>(serialized_emm), Return(OkStatus())));
|
||||
|
||||
uint8_t counter = 0;
|
||||
uint8_t packet[kTsPacketSize];
|
||||
ssize_t packet_size = 1;
|
||||
uint8_t packet[1];
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status =
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, &packet_size);
|
||||
emm_gen.GenerateEmmTsPackets(kTestPid, &counter, packet, &bytes_modified);
|
||||
EXPECT_EQ(status.error_code(), error::INVALID_ARGUMENT);
|
||||
EXPECT_THAT(status.error_message(), HasSubstr("buffer is too small"));
|
||||
}
|
||||
|
||||
@@ -283,16 +283,16 @@ Status Emmg::GenerateEmmData() {
|
||||
}
|
||||
|
||||
uint8_t datagram[kMaxPossibleTsPacketsSizeBytes];
|
||||
ssize_t datagram_size = kMaxPossibleTsPacketsSizeBytes;
|
||||
ssize_t bytes_modified = 0;
|
||||
status = emm_impl_->GenerateEmmTsPackets(
|
||||
/*pid=*/0, &continuity_counter_, datagram, &datagram_size);
|
||||
/*pid=*/0, &continuity_counter_, datagram, &bytes_modified);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (datagram_size <= 0 || datagram_size % kTsPacketSize != 0) {
|
||||
if (bytes_modified <= 0 || bytes_modified % kTsPacketSize != 0) {
|
||||
return {error::INTERNAL, "Failed to generate EMM TS packet"};
|
||||
}
|
||||
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, datagram_size, request_,
|
||||
simulcrypt_util::AddParam(EMMG_DATAGRAM, datagram, bytes_modified, request_,
|
||||
&request_length_);
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
@@ -48,24 +48,28 @@ ContinuityCounter Increment(ContinuityCounter continuity_counter) {
|
||||
|
||||
Status InsertDataAsTsPacket(const std::string& insert_data, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
uint8_t* buffer, ssize_t* bytes_modified) {
|
||||
absl::Span<uint8_t> buffer) {
|
||||
if (insert_data.empty()) {
|
||||
return {error::INVALID_ARGUMENT, "insert_data must not be empty"};
|
||||
}
|
||||
if (cc == nullptr || buffer == nullptr || bytes_modified == nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"cc, buffer and bytes_modified must not be null"};
|
||||
}
|
||||
if (*bytes_modified % kTsPacketSize != 0) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"bytes_modified must be a multiple of TS packet size (188 bytes)"};
|
||||
if (cc == nullptr) {
|
||||
return {error::INVALID_ARGUMENT, "Continuity counter must not be null"};
|
||||
}
|
||||
if (insert_data.size() > kMaxSectionSize) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Data is too large. Max: ", kMaxSectionSize)};
|
||||
}
|
||||
size_t expected_buffer_size =
|
||||
CalculateTsBufferSizeForEcmEmm(insert_data.size());
|
||||
if (buffer.size() != expected_buffer_size) {
|
||||
return {
|
||||
error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Invalid buffer size. Expected: ", expected_buffer_size,
|
||||
" bytes , but got ", buffer.size(), " bytes.")};
|
||||
}
|
||||
|
||||
int reading_index = 0;
|
||||
int buffer_write_offset = 0;
|
||||
while (reading_index < insert_data.size()) {
|
||||
bool is_first_packet = reading_index == 0;
|
||||
// Create a TS payload of 184 bytes (Max TS size - ts header length).
|
||||
@@ -110,9 +114,9 @@ Status InsertDataAsTsPacket(const std::string& insert_data, ProgramId pid,
|
||||
if (serialized_ts_packet.size() != kTsPacketSize) {
|
||||
return {error::INTERNAL, "Unexpected TS packet size generated."};
|
||||
}
|
||||
memcpy(buffer + *bytes_modified, serialized_ts_packet.data(),
|
||||
memcpy(buffer.data() + buffer_write_offset, serialized_ts_packet.data(),
|
||||
serialized_ts_packet.size());
|
||||
*bytes_modified += serialized_ts_packet.size();
|
||||
buffer_write_offset += serialized_ts_packet.size();
|
||||
}
|
||||
|
||||
return OkStatus();
|
||||
@@ -157,19 +161,17 @@ void Host32ToBigEndian(void* destination, const uint32_t* source) {
|
||||
}
|
||||
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified) {
|
||||
return InsertDataAsTsPacket(ecm, pid, table_id, cc, buffer, bytes_modified);
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
absl::Span<uint8_t> buffer) {
|
||||
return InsertDataAsTsPacket(ecm, pid, table_id, cc, buffer);
|
||||
}
|
||||
|
||||
Status InsertEmmAsTsPacket(const std::string& emm, ProgramId pid,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified) {
|
||||
return InsertDataAsTsPacket(emm, pid, kEmmTableId, cc, buffer,
|
||||
bytes_modified);
|
||||
ContinuityCounter* cc, absl::Span<uint8_t> buffer) {
|
||||
return InsertDataAsTsPacket(emm, pid, kEmmTableId, cc, buffer);
|
||||
}
|
||||
|
||||
uint16_t CalculateTsBufferSizeForEcmEmm(uint16_t ecm_emm_size) {
|
||||
size_t CalculateTsBufferSizeForEcmEmm(size_t ecm_emm_size) {
|
||||
return kTsPacketSize *
|
||||
std::ceil(static_cast<float>(kSectionHeaderSize + ecm_emm_size) /
|
||||
kMaxTsPayloadSize);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
@@ -48,31 +49,22 @@ void Host32ToBigEndian(void* destination, const uint32_t* source);
|
||||
// table ID so the client knows this is an important ECM it should process.
|
||||
// - |cc| is the continuity counter to use in the header, which will also be
|
||||
// incremented by this function.
|
||||
// - |buffer| is the output buffer. Must be big enough to hold an additional
|
||||
// 188 bytes * number of TS packets needed to put |ecm|.
|
||||
// - |bytes_modified| the number of bytes which have already been modified in
|
||||
// the |buffer| and is used as an offset.
|
||||
// |bytes_modified| will be incremented by a multiple of 188 if insertion of
|
||||
// ECM into |buffer| is successful.
|
||||
// - |buffer| is the output buffer. The size of |buffer| must be equal to
|
||||
// CalculateTsBufferSizeForEcmEmm(ecm.size()).
|
||||
Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
|
||||
uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified);
|
||||
uint8_t table_id, ContinuityCounter* cc,
|
||||
absl::Span<uint8_t> buffer);
|
||||
|
||||
// Packages an EMM as a TS packet and inserts it into a buffer.
|
||||
// Args:
|
||||
// - |emm| is the serialized EMM.
|
||||
// - |pid| is the PID used for ECMs in the TS header.
|
||||
// - |pid| is the PID used for EMMs in the TS header.
|
||||
// - |cc| is the continuity counter to use in the header, which will also be
|
||||
// incremented by this function.
|
||||
// - |buffer| is the output buffer. Must be big enough to hold an additional
|
||||
// 188 bytes * number of TS packets needed to put |emm|.
|
||||
// - |bytes_modified| the number of bytes which have already been modified in
|
||||
// the |buffer| and is used as an offset.
|
||||
// |bytes_modified| will be incremented by a multiple of 188 if insertion of
|
||||
// ECM into |buffer| is successful.
|
||||
// - |buffer| is the output buffer. The size of |buffer| must be equal to
|
||||
// CalculateTsBufferSizeForEcmEmm(emm.size()).
|
||||
Status InsertEmmAsTsPacket(const std::string& emm, ProgramId pid,
|
||||
ContinuityCounter* cc, uint8_t* buffer,
|
||||
ssize_t* bytes_modified);
|
||||
ContinuityCounter* cc, absl::Span<uint8_t> buffer);
|
||||
|
||||
// Calculates the buffer size needed to hold TS packets generated by inserting
|
||||
// ECM/EMM calls InsertEcmAsTsPacket() and InsertEmmAsTsPacket(). The returned
|
||||
@@ -80,7 +72,7 @@ Status InsertEmmAsTsPacket(const std::string& emm, ProgramId pid,
|
||||
// Args:
|
||||
// - |ecm_emm_size| is the size of the serialized ECM or EMM that will be
|
||||
// inserted to TS packets.
|
||||
uint16_t CalculateTsBufferSizeForEcmEmm(uint16_t ecm_emm_size);
|
||||
size_t CalculateTsBufferSizeForEcmEmm(size_t ecm_emm_size);
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -152,98 +153,102 @@ TEST(InsertEcmAsTsPacketTest, SingleTsPacket) {
|
||||
// declare variables used by InsertEcmAsTsPacket()
|
||||
ContinuityCounter ecm_cc = 0;
|
||||
uint8_t buffer[kTsPacketSize]; // output ts packet size
|
||||
ssize_t output_bytes_modified = 0;
|
||||
// convert kEcmPayload[] into a std::string to be accepted by the method
|
||||
std::string ecm_data(kEcmPayload);
|
||||
|
||||
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, kTestPid, kTsPacketTableId80, &ecm_cc,
|
||||
buffer, &output_bytes_modified));
|
||||
buffer));
|
||||
|
||||
EXPECT_EQ(memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)), 0);
|
||||
EXPECT_EQ(ecm_cc, 1);
|
||||
EXPECT_EQ(output_bytes_modified, kTsPacketSize);
|
||||
}
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, MultipleTsPackets) {
|
||||
ContinuityCounter ecm_cc = 0;
|
||||
uint8_t buffer[kTsPacketSize * 2];
|
||||
ssize_t output_bytes_modified = 0;
|
||||
std::string long_payload(kLongPayload);
|
||||
|
||||
EXPECT_OK(InsertEcmAsTsPacket(long_payload, kTestPid, kTsPacketTableId80,
|
||||
&ecm_cc, buffer, &output_bytes_modified));
|
||||
&ecm_cc, buffer));
|
||||
|
||||
EXPECT_EQ(memcmp(kExpectedLongTs, buffer, sizeof(buffer)), 0);
|
||||
EXPECT_EQ(ecm_cc, 2);
|
||||
EXPECT_EQ(output_bytes_modified, kTsPacketSize * 2);
|
||||
}
|
||||
|
||||
TEST(InsertEmmAsTsPacketTest, SingleTsPacket) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize];
|
||||
ssize_t output_bytes_modified = 0;
|
||||
std::string payload(kEcmPayload);
|
||||
|
||||
EXPECT_OK(InsertEmmAsTsPacket(payload, kTestPid, &counter, buffer,
|
||||
&output_bytes_modified));
|
||||
EXPECT_OK(InsertEmmAsTsPacket(payload, kTestPid, &counter, buffer));
|
||||
|
||||
char expectedEmmPacket[sizeof(kExpectedEcmPacket)];
|
||||
memcpy(expectedEmmPacket, kExpectedEcmPacket, sizeof(kExpectedEcmPacket));
|
||||
expectedEmmPacket[5] = 0x82; // Expected table id for emm
|
||||
EXPECT_EQ(memcmp(expectedEmmPacket, buffer, sizeof(buffer)), 0);
|
||||
EXPECT_EQ(counter, 1);
|
||||
EXPECT_EQ(output_bytes_modified, kTsPacketSize);
|
||||
}
|
||||
|
||||
TEST(InsertEmmAsTsPacketTest, MultipleTsPackets) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize * 2];
|
||||
ssize_t output_bytes_modified = 0;
|
||||
std::string long_payload(kLongPayload);
|
||||
|
||||
EXPECT_OK(InsertEmmAsTsPacket(long_payload, kTestPid, &counter, buffer,
|
||||
&output_bytes_modified));
|
||||
EXPECT_OK(InsertEmmAsTsPacket(long_payload, kTestPid, &counter, buffer));
|
||||
|
||||
char expectedEmmPacket[sizeof(kExpectedLongTs)];
|
||||
memcpy(expectedEmmPacket, kExpectedLongTs, sizeof(kExpectedLongTs));
|
||||
expectedEmmPacket[5] = 0x82; // Expected table id for emm
|
||||
EXPECT_EQ(memcmp(expectedEmmPacket, buffer, sizeof(buffer)), 0);
|
||||
EXPECT_EQ(counter, 2);
|
||||
EXPECT_EQ(output_bytes_modified, kTsPacketSize * 2);
|
||||
}
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, InvalidArgument) {
|
||||
ContinuityCounter counter = 0;
|
||||
TEST(InsertEcmAsTsPacketTest, NullContinuityCounterFail) {
|
||||
uint8_t buffer[kTsPacketSize];
|
||||
ssize_t output_bytes_modified = 0;
|
||||
std::string payload(kEcmPayload);
|
||||
|
||||
// Continuity counter is null.
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, nullptr,
|
||||
buffer, &output_bytes_modified)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
// Buffer is null.
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, &counter,
|
||||
nullptr, &output_bytes_modified)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
// Output bytes modified is null.
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, &counter,
|
||||
buffer, nullptr)
|
||||
buffer)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
// Payload is empty.
|
||||
EXPECT_EQ(InsertEcmAsTsPacket("", kTestPid, kTsPacketTableId80, &counter,
|
||||
buffer, &output_bytes_modified)
|
||||
TEST(InsertEcmAsTsPacketTest, PayloadEmptyFail) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize];
|
||||
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(/*ecm=*/"", kTestPid, kTsPacketTableId80,
|
||||
&counter, buffer)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
// Payload is too large.
|
||||
TEST(InsertEcmAsTsPacketTest, PayloadTooLargeFail) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize];
|
||||
std::string too_large_payload(kMaxSectionSize + 1, '\x11');
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(too_large_payload, kTestPid, kTsPacketTableId80,
|
||||
&counter, buffer, &output_bytes_modified)
|
||||
&counter, buffer)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, SmallBufferFail) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize];
|
||||
std::string double_size_payload(kTsPacketSize * 2, '\x11');
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(double_size_payload, kTestPid,
|
||||
kTsPacketTableId80, &counter, buffer)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TEST(InsertEcmAsTsPacketTest, LargeBufferFail) {
|
||||
ContinuityCounter counter = 0;
|
||||
uint8_t buffer[kTsPacketSize * 2];
|
||||
std::string payload(kEcmPayload);
|
||||
EXPECT_EQ(InsertEcmAsTsPacket(payload, kTestPid, kTsPacketTableId80, &counter,
|
||||
buffer)
|
||||
.error_code(),
|
||||
error::INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ cc_library(
|
||||
":wv_cas_types",
|
||||
"//base",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
],
|
||||
@@ -184,6 +185,7 @@ cc_library(
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/memory",
|
||||
"@abseil_repo//absl/types:span",
|
||||
"//common:status",
|
||||
"//media_cas_packager_sdk/internal:emm",
|
||||
],
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "common/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -35,6 +37,10 @@ EcmInitParameters ConvertToEcmInitParameters(
|
||||
init_params.key_rotation_enabled = ecm_parameters.key_rotation_enabled;
|
||||
init_params.crypto_mode = ecm_parameters.crypto_mode;
|
||||
init_params.age_restriction = ecm_parameters.age_restriction;
|
||||
init_params.cas_id = ecm_parameters.cas_id;
|
||||
init_params.ecm_version = ecm_parameters.ecm_version;
|
||||
init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key;
|
||||
|
||||
return init_params;
|
||||
}
|
||||
} // namespace
|
||||
@@ -42,25 +48,28 @@ EcmInitParameters ConvertToEcmInitParameters(
|
||||
WvCasEcm::WvCasEcm(
|
||||
const WvCasEcmParameters& ecm_parameters,
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
|
||||
CHECK(!injected_entitlements.empty());
|
||||
ecm_param_ = ecm_parameters;
|
||||
injected_entitlements_.assign(injected_entitlements.begin(),
|
||||
injected_entitlements.end());
|
||||
ecm_ = absl::make_unique<Ecm>();
|
||||
Status status = ecm_->Initialize(ConvertToEcmInitParameters(ecm_parameters),
|
||||
injected_entitlements);
|
||||
CHECK(status.ok()) << "Failed to get initialize ECM class." << status;
|
||||
}
|
||||
WvCasEcm::~WvCasEcm() = default;
|
||||
|
||||
void WvCasEcm::SetFingerprinting(
|
||||
const EcmFingerprintingParams* fingerprinting) {
|
||||
ecm_->SetFingerprinting(fingerprinting);
|
||||
}
|
||||
|
||||
void WvCasEcm::SetServiceBlocking(
|
||||
const EcmServiceBlockingParams* service_blocking) {
|
||||
ecm_->SetServiceBlocking(service_blocking);
|
||||
}
|
||||
|
||||
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
|
||||
const WvCasContentKeyInfo& odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
|
||||
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to get initialize ECM class." << status;
|
||||
return status;
|
||||
}
|
||||
|
||||
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
|
||||
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key);
|
||||
// Make content key to 16 bytes if crypto mode is Csa2.
|
||||
@@ -74,22 +83,13 @@ Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
|
||||
entitled_odd_key.key_value + entitled_odd_key.key_value;
|
||||
}
|
||||
}
|
||||
return cas_ecm->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
|
||||
return ecm_->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
|
||||
serialized_ecm);
|
||||
}
|
||||
|
||||
Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const {
|
||||
// Create an instance of Ecm in order to set the entitlement keys.
|
||||
auto cas_ecm = absl::make_unique<Ecm>();
|
||||
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
|
||||
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to get initialize ECM class." << status;
|
||||
return status;
|
||||
}
|
||||
|
||||
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
|
||||
// Make content key to 16 bytes if crypto mode is Csa2.
|
||||
if (ecm_param_.crypto_mode == CryptoMode::kDvbCsa2) {
|
||||
@@ -97,14 +97,27 @@ Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
|
||||
entitled_key.key_value = entitled_key.key_value + entitled_key.key_value;
|
||||
}
|
||||
}
|
||||
return cas_ecm->GenerateSingleKeyEcm(&entitled_key, track_type,
|
||||
serialized_ecm);
|
||||
return ecm_->GenerateSingleKeyEcm(&entitled_key, track_type, serialized_ecm);
|
||||
}
|
||||
|
||||
Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet) {
|
||||
return Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter, packet);
|
||||
uint8_t* packet, ssize_t* packet_size) {
|
||||
if (continuity_counter == nullptr || packet == nullptr ||
|
||||
packet_size == nullptr) {
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"continuity_counter, packet and packet_size must not be null"};
|
||||
}
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter,
|
||||
absl::MakeSpan(packet, *packet_size),
|
||||
&bytes_modified);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
*packet_size = bytes_modified;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -19,6 +20,8 @@
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
class Ecm;
|
||||
|
||||
// Information needed to generate content key portion of ECM.
|
||||
// Fields:
|
||||
// |key_id| key ID for the content key, must be 16 bytes.
|
||||
@@ -44,11 +47,19 @@ struct WvCasContentKeyInfo {
|
||||
// |crypto_mode| the encryption mode used for the content stream.
|
||||
// A constant of type CryptoMode.
|
||||
// |age_restriction| minimum age required; the value represents actual age.
|
||||
// |cas_id| CA system id that is in the ECM. Must be 0x4AD4 or 0x56C0~0x56C9
|
||||
// (all inclusive).
|
||||
// |ecm_version| version of generated ECM.
|
||||
// |ecc_private_signing_key| Private signing key used to sign ECM data. Must
|
||||
// be an elliptic-curve cryptography key.
|
||||
struct WvCasEcmParameters {
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
bool key_rotation_enabled = true;
|
||||
CryptoMode crypto_mode = CryptoMode::kAesCtr;
|
||||
uint8_t age_restriction = 0;
|
||||
uint16_t cas_id = 0x4AD4;
|
||||
EcmVersion ecm_version = EcmVersion::kV2;
|
||||
std::string ecc_private_signing_key;
|
||||
};
|
||||
|
||||
// Class for generating Widevine CAS ECMs.
|
||||
@@ -66,10 +77,23 @@ class WvCasEcm {
|
||||
const std::vector<EntitlementKeyInfo>& injected_entitlements);
|
||||
WvCasEcm(const WvCasEcm&) = delete;
|
||||
WvCasEcm& operator=(const WvCasEcm&) = delete;
|
||||
virtual ~WvCasEcm() = default;
|
||||
virtual ~WvCasEcm();
|
||||
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
// Set fingerprinting info that will be embedded into the generated ECM. The
|
||||
// configuration will be used in all following ECMs generated by calling
|
||||
// GenerateEcm() or GenerateSingleKeyEcm().
|
||||
// |fingerprinting| may be set to nullptr to clear the fingerprinting info.
|
||||
virtual void SetFingerprinting(const EcmFingerprintingParams* fingerprinting);
|
||||
|
||||
// Set service blocking info that will be embedded into the generated ECM. The
|
||||
// configuration will be used in all following ECMs generated by calling
|
||||
// GenerateEcm() or GenerateSingleKeyEcm().
|
||||
// |service_blocking| may be set to nullptr to clear the service blocking
|
||||
// info.
|
||||
virtual void SetServiceBlocking(
|
||||
const EcmServiceBlockingParams* service_blocking);
|
||||
|
||||
// Constructs a Widevine ECM using the provided key info.
|
||||
// Args:
|
||||
// |even_key| information for even key to be encoded into ECM.
|
||||
// |odd_key| information for odd key to be encoded into ECM.
|
||||
@@ -78,15 +102,14 @@ class WvCasEcm {
|
||||
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
|
||||
// consistent with the initialized settings.
|
||||
// The even_key and odd_key will be wrapped using the appropriate
|
||||
// entitlement key. Wrapping modifies the original structure.
|
||||
// entitlement key.
|
||||
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
|
||||
const WvCasContentKeyInfo& odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm) const;
|
||||
|
||||
// Accept a key and IV and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes). This call is specifically for the case
|
||||
// where key rotation is disabled.
|
||||
// Constructs a Widevine ECM using the provided key info. This call is
|
||||
// specifically for the case where key rotation is disabled.
|
||||
// Args:
|
||||
// |key| information for key to be encoded into ECM.
|
||||
// |track_type| the track that the key is being used to encrypt.
|
||||
@@ -112,16 +135,18 @@ class WvCasEcm {
|
||||
// it will be incremented, only last 4 bits are used
|
||||
// - |packet| a buffer of size at least 188 bytes to be used to return
|
||||
// the generated TS packet
|
||||
// - |packet_size| is the size of the allocated |packet|. It will be updated
|
||||
// as the number of bytes actually used.
|
||||
//
|
||||
// Returns:
|
||||
// - A status indicating whether there was any error during processing
|
||||
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
|
||||
uint8_t table_id, uint8_t* continuity_counter,
|
||||
uint8_t* packet);
|
||||
uint8_t* packet, ssize_t* packet_size);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ecm> ecm_;
|
||||
WvCasEcmParameters ecm_param_;
|
||||
std::vector<EntitlementKeyInfo> injected_entitlements_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -36,6 +36,21 @@ void WvCasEcmgClientHandler::SetCustomEntitlementKeyFetcherFunc(
|
||||
return inner_handler_->SetCustomEntitlementKeyFetcherFunc(fetcher);
|
||||
}
|
||||
|
||||
void WvCasEcmgClientHandler::SetCustomAccessCriteriaProcessFunc(
|
||||
CustomAccessCriteriaProcessFunc ac_processor) {
|
||||
return inner_handler_->SetCustomAccessCriteriaProcessFunc(ac_processor);
|
||||
}
|
||||
|
||||
void WvCasEcmgClientHandler::SetFingerprintingSettingFunc(
|
||||
FingerprintingSettingFunc fingerprinting_func) {
|
||||
return inner_handler_->SetFingerprintingSettingFunc(fingerprinting_func);
|
||||
}
|
||||
|
||||
void WvCasEcmgClientHandler::SetServiceBlockingSettingFunc(
|
||||
ServiceBlockingSettingFunc service_blocking_func) {
|
||||
return inner_handler_->SetServiceBlockingSettingFunc(service_blocking_func);
|
||||
}
|
||||
|
||||
Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
|
||||
const char* const request_buffer,
|
||||
size_t response_buffer_size,
|
||||
|
||||
@@ -36,6 +36,27 @@ class WvCasEcmgClientHandler {
|
||||
// Calling this function is optional.
|
||||
void SetCustomEntitlementKeyFetcherFunc(EntitlementKeyFetcherFunc fetcher);
|
||||
|
||||
// Sets the custom access criteria processing function used by ECMG to get
|
||||
// information including entitlement keys, content iv, crypto mode, etc.
|
||||
// Calling this function is optional: If the function is set, access criteria
|
||||
// received in the CwProvision message from SCS will be processed by this
|
||||
// callback function (SDK will no longer process the access criteria). If not
|
||||
// set, access criteria will be processed by the SDK.
|
||||
void SetCustomAccessCriteriaProcessFunc(
|
||||
CustomAccessCriteriaProcessFunc ac_processor);
|
||||
|
||||
// Sets the fingerprinting setting function, which will be called upon each
|
||||
// CwProvisioning request. It controls the fingerprinting information carried
|
||||
// in ECMs.
|
||||
void SetFingerprintingSettingFunc(
|
||||
FingerprintingSettingFunc fingerprinting_func);
|
||||
|
||||
// Sets the service blocking setting function, which will be called upon each
|
||||
// CwProvisioning request. It controls the service blocking information
|
||||
// carried in ECMs.
|
||||
void SetServiceBlockingSettingFunc(
|
||||
ServiceBlockingSettingFunc service_blocking_func);
|
||||
|
||||
// Handles a |request| from the SCS client. If any response is generated, it
|
||||
// will return the response via |response_buffer| and |response_length|.
|
||||
// Args:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "media_cas_packager_sdk/internal/emm.h"
|
||||
|
||||
namespace widevine {
|
||||
@@ -93,8 +94,16 @@ Status WvCasEmm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
|
||||
return {error::INVALID_ARGUMENT,
|
||||
"continuity_counter, packet and packet_size must not be null"};
|
||||
}
|
||||
return emm_->GenerateEmmTsPackets(pid, continuity_counter, packet,
|
||||
packet_size);
|
||||
ssize_t bytes_modified = 0;
|
||||
Status status = emm_->GenerateEmmTsPackets(
|
||||
pid, continuity_counter, absl::MakeSpan(packet, *packet_size),
|
||||
&bytes_modified);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
*packet_size = bytes_modified;
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "media_cas_packager_sdk/internal/emm.h"
|
||||
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::ElementsAreArray;
|
||||
using ::testing::IsEmpty;
|
||||
@@ -25,6 +26,7 @@ using ::testing::Pointwise;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::SetArrayArgument;
|
||||
using ::testing::WithArg;
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
@@ -62,8 +64,8 @@ class MockEmm : public Emm {
|
||||
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm),
|
||||
(const, override));
|
||||
MOCK_METHOD(Status, GenerateEmmTsPackets,
|
||||
(uint16_t pid, uint8_t* continuity_counter, uint8_t* packet,
|
||||
ssize_t* packet_size),
|
||||
(uint16_t pid, uint8_t* continuity_counter,
|
||||
const absl::Span<uint8_t> packet, ssize_t* bytes_modified),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
@@ -204,11 +206,12 @@ TEST_F(WvCasEmmTest, GenerateEmmTsPacketsSuccess) {
|
||||
uint8_t expected_continuity_counter = 2;
|
||||
uint8_t expected_packet[] = {1, 2, 3, 4, 5};
|
||||
EXPECT_CALL(*mock_internal_emm_,
|
||||
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
|
||||
.WillOnce(
|
||||
DoAll(SetArgPointee<1>(expected_continuity_counter),
|
||||
SetArrayArgument<2>(expected_packet,
|
||||
expected_packet + sizeof(expected_packet)),
|
||||
GenerateEmmTsPackets(kTestPid, NotNull(), _, NotNull()))
|
||||
.WillOnce(DoAll(
|
||||
SetArgPointee<1>(expected_continuity_counter),
|
||||
WithArg<2>([&expected_packet](absl::Span<uint8_t> packet) {
|
||||
memcpy(packet.data(), expected_packet, sizeof(expected_packet));
|
||||
}),
|
||||
SetArgPointee<3>(sizeof(expected_packet)), Return(OkStatus())));
|
||||
|
||||
uint8_t continuity_counter = 1;
|
||||
@@ -222,7 +225,7 @@ TEST_F(WvCasEmmTest, GenerateEmmTsPacketsSuccess) {
|
||||
|
||||
TEST_F(WvCasEmmTest, GenerateEmmTsPacketsFail) {
|
||||
EXPECT_CALL(*mock_internal_emm_,
|
||||
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
|
||||
GenerateEmmTsPackets(kTestPid, NotNull(), _, NotNull()))
|
||||
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
|
||||
|
||||
uint8_t continuity_counter = 1;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -65,6 +66,8 @@ struct EntitlementKeyInfo {
|
||||
std::string key_value; // must be 32 bytes.
|
||||
};
|
||||
|
||||
enum class EcmVersion : int { kV2 = 0, kV3 = 1 };
|
||||
|
||||
// A struct that captures the Simulcrypt ECMG configurations. Most fields are
|
||||
// Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10)
|
||||
// Section 5.3).
|
||||
@@ -109,6 +112,11 @@ struct EcmgConfig {
|
||||
// will be included in the ECM. If new crypto_mode is received from SCS, ECM
|
||||
// will use the new one.
|
||||
CryptoMode crypto_mode;
|
||||
// Version of generated ECM.
|
||||
EcmVersion ecm_version = EcmVersion::kV2;
|
||||
// Private signing key used to sign ECM data. Must be an elliptic-curve
|
||||
// cryptography key.
|
||||
std::string ecc_private_signing_key;
|
||||
};
|
||||
|
||||
// A custom entitlement key fetching function used by ECMG to fetch entitlement
|
||||
@@ -127,6 +135,82 @@ struct EcmgConfig {
|
||||
using EntitlementKeyFetcherFunc = std::vector<EntitlementKeyInfo> (*)(
|
||||
uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id);
|
||||
|
||||
struct EcmgCustomParameters {
|
||||
// Minimum age required for the content. The value represents actual age.
|
||||
// A negative value means the field is not set.
|
||||
int age_restriction = -1;
|
||||
// The encryption mode used for the content stream.
|
||||
std::string crypto_mode;
|
||||
// Initial vector used in the content encryption, must be 8 (DvbCsa2) or 16
|
||||
// bytes (all other crypto modes). The size of the vector must not exceed 2.
|
||||
// The first entry is for the even key slot, the second entry is for the odd
|
||||
// key slot. It is suggested that the same IV used for both even and odd key
|
||||
// slot for better decrypting performance.
|
||||
std::vector<std::string> content_ivs;
|
||||
// Entitlement keys used to encrypt the content keys (control words).
|
||||
// The size of the vector must not exceed 2. When the vector size is 2, one of
|
||||
// them must be specified as even key and the other must be odd key (sequence
|
||||
// does not matter).
|
||||
std::vector<EntitlementKeyInfo> entitlement_keys;
|
||||
// Fingerprinting control that will be embedded in ECM. The control is opaque
|
||||
// to Widevine.
|
||||
std::string fingerprinting_control;
|
||||
// Devices that should have service blocking enforced. The blocking starts
|
||||
// when the ECM is received, and stops util the device is no longer in
|
||||
// |device_groups|.
|
||||
std::vector<std::string> service_blocking_groups;
|
||||
};
|
||||
|
||||
// A custom access control processing function used by ECMG to get information
|
||||
// including entitlement keys, content iv, crypto mode, etc. If set, the
|
||||
// function will be called when an access criteria is received in the
|
||||
// CwProvision message from SCS. The access criteria will no longer be processed
|
||||
// by the SDK.
|
||||
// Args:
|
||||
// |channel_id| is the channel id received at ECMG from SCS when setting up
|
||||
// the channel.
|
||||
// |stream_id| is the stream id received at ECMG from SCS when setting up
|
||||
// the stream.
|
||||
// |access_criteria| the received access criteria from SCS.
|
||||
// Returns EcmgCustomParameters. Negative or empty fields will be ignored.
|
||||
typedef std::function<EcmgCustomParameters(uint16_t channel_id, uint16_t stream_id,
|
||||
const std::string& access_criteria)>
|
||||
CustomAccessCriteriaProcessFunc;
|
||||
|
||||
struct EcmFingerprintingParams {
|
||||
// The |control| is opaque to Widevine.
|
||||
std::string control;
|
||||
};
|
||||
|
||||
// Function that will be called upon each CwProvisioning request. It controls
|
||||
// the fingerprinting information carried in ECMs.
|
||||
// Args:
|
||||
// |channel_id| is the channel id received at ECMG from SCS when setting up
|
||||
// the channel.
|
||||
// |stream_id| is the stream id received at ECMG from SCS when setting up
|
||||
// the stream.
|
||||
typedef std::function<EcmFingerprintingParams(uint16_t channel_id,
|
||||
uint16_t stream_id)>
|
||||
FingerprintingSettingFunc;
|
||||
|
||||
struct EcmServiceBlockingParams {
|
||||
// Devices that should have service blocking enforced. The blocking starts
|
||||
// when the ECM is received, and stops util the device is no longer in
|
||||
// |device_groups|.
|
||||
std::vector<std::string> device_groups;
|
||||
};
|
||||
|
||||
// Function that will be called upon each CwProvisioning request. It controls
|
||||
// the service blocking information carried in ECMs.
|
||||
// Args:
|
||||
// |channel_id| is the channel id received at ECMG from SCS when setting up
|
||||
// the channel.
|
||||
// |stream_id| is the stream id received at ECMG from SCS when setting up
|
||||
// the stream.
|
||||
typedef std::function<EcmServiceBlockingParams(uint16_t channel_id,
|
||||
uint16_t stream_id)>
|
||||
ServiceBlockingSettingFunc;
|
||||
|
||||
struct WvCasEncryptionRequest {
|
||||
std::string content_id;
|
||||
std::string provider;
|
||||
|
||||
@@ -49,6 +49,13 @@ ABSL_FLAG(std::string, crypto_mode, "AesCtr",
|
||||
"Default encryption mode if not provided in API calls. Choices are "
|
||||
"\"AesCtr\", \"AesCbc\", \"DvbCsa2\", \"DvbCsa3\", \"AesOfb\" and "
|
||||
"\"AesScte\".");
|
||||
ABSL_FLAG(
|
||||
int32_t, ecm_version, 2,
|
||||
"Version of the generated ECM. Crrently supported versions are 2 and 3.");
|
||||
ABSL_FLAG(
|
||||
std::string, ecc_private_signing_key, "",
|
||||
"Private signing key used to sign ECM data. Must be an elliptic-curve "
|
||||
"cryptography key.");
|
||||
|
||||
#define LISTEN_QUEUE_SIZE (20)
|
||||
#define BUFFER_SIZE (1024)
|
||||
@@ -71,6 +78,13 @@ void BuildEcmgConfig(EcmgConfig* config) {
|
||||
CHECK(StringToCryptoMode(absl::GetFlag(FLAGS_crypto_mode),
|
||||
&config->crypto_mode))
|
||||
<< "Unknown crypto mode.";
|
||||
CHECK(absl::GetFlag(FLAGS_ecm_version) <= 3)
|
||||
<< "Invalid ecm version. Must be 2 or 3.";
|
||||
config->ecm_version = absl::GetFlag(FLAGS_ecm_version) <= 2
|
||||
? widevine::cas::EcmVersion::kV2
|
||||
: widevine::cas::EcmVersion::kV3;
|
||||
config->ecc_private_signing_key =
|
||||
absl::GetFlag(FLAGS_ecc_private_signing_key);
|
||||
}
|
||||
|
||||
void PrintMessage(const std::string& description, const char* const message,
|
||||
|
||||
@@ -50,3 +50,62 @@ message EmmPayload {
|
||||
repeated Fingerprinting fingerprinting = 1;
|
||||
repeated ServiceBlocking service_blocking = 2;
|
||||
}
|
||||
|
||||
message EcmMetaData {
|
||||
enum CipherMode {
|
||||
UNSPECIFIED = 0;
|
||||
AES_CBC = 1;
|
||||
AES_CTR = 2;
|
||||
DVB_CSA2 = 3;
|
||||
DVB_CSA3 = 4;
|
||||
AES_OFB = 5;
|
||||
AES_SCTE52 = 6;
|
||||
}
|
||||
// Required. The cipher mode used to encrypt/decrypt the content.
|
||||
optional CipherMode cipher_mode = 1;
|
||||
// Optional. The minimum age required to watch the content. The value
|
||||
// represents actual age, with 0 means no restriction.
|
||||
optional uint32 age_restriction = 2 [default = 0];
|
||||
}
|
||||
|
||||
message EcmKeyData {
|
||||
// The wrapped content key data (aka control word).
|
||||
// Required.
|
||||
optional bytes wrapped_key_data = 1;
|
||||
// The ID of the entitlement key used to wrap the content key. The secure key
|
||||
// data associated with this ID is held by the license server. The client gets
|
||||
// the key from the license server through a license request.
|
||||
// Required for the even key data, optional for the odd key data if it is the
|
||||
// same as the even key data.
|
||||
optional bytes entitlement_key_id = 2;
|
||||
// IV for decrypting the wrapped_key_data.
|
||||
// Required for the even key data, optional for the odd key data if it is the
|
||||
// same as the even key data.
|
||||
optional bytes wrapped_key_iv = 3;
|
||||
// IV for decrypting the content stream.
|
||||
// Optional. If not specified in the even key data, 8 bytes 0x00 will be used;
|
||||
// If not specified in the odd key data, the same content iv in the even key
|
||||
// data will be used.
|
||||
optional bytes content_iv = 4;
|
||||
}
|
||||
|
||||
message EcmPayload {
|
||||
// Required. Meta info carried by the ECM.
|
||||
optional EcmMetaData meta_data = 1;
|
||||
// Required. The key data for the even slot.
|
||||
optional EcmKeyData even_key_data = 2;
|
||||
// Optional. The key data for the odd slot if key rotation is enabled.
|
||||
optional EcmKeyData odd_key_data = 3;
|
||||
// Optional. Widevine fingerprinting information.
|
||||
optional Fingerprinting fingerprinting = 4;
|
||||
// Optional. Widevine service blocking information.
|
||||
optional ServiceBlocking service_blocking = 5;
|
||||
}
|
||||
|
||||
// The payload field for an ECM with signature.
|
||||
message SignedEcmPayload {
|
||||
// Serialized EcmPayload.
|
||||
optional bytes serialized_payload = 1;
|
||||
// ECC (Elliptic Curve Cryptography) signature of |serialized_payload|.
|
||||
optional bytes signature = 2;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user