Decouple key fetcher; Update ECMG API

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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