Decouple key fetcher; Update ECMG API
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
61
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc
Normal file
61
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc
Normal 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
|
||||
39
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h
Normal file
39
media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.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_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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=".
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user