Entitlement rotation support

Updates also include:
- Add APIs to query current Simulcrypt channel & stream status;
- EMM format change (used only to carry fingerprinting and service
blocking info);
- Key fetcher example to use curl key fetcher.
This commit is contained in:
Lu Chen
2021-06-29 14:51:49 -07:00
parent f04e15c48c
commit b3a5fff77d
42 changed files with 1425 additions and 876 deletions

View File

@@ -206,6 +206,7 @@ cc_library(
"//base",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/types:span",
"//common:status",
"//media_cas_packager_sdk/internal:ecmg_client_handler",
],
@@ -242,6 +243,7 @@ cc_test(
":wv_cas_types",
"//testing:gunit_main",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/types:span",
"//common:status",
"//media_cas_packager_sdk/internal:ecmg_client_handler",
],

View File

@@ -10,6 +10,7 @@
#include <bitset>
#include <cstdint>
#include <string>
#include <vector>
#include "glog/logging.h"
@@ -51,16 +52,11 @@ constexpr uint32_t kReservedBit = 0x0007;
// The range of valid PIDs, from section 2.4.3.3, and table 2-3.
constexpr uint32_t kMinValidPID = 0x0010;
constexpr uint32_t kMaxValidPID = 0x1FFE;
// Maximum number of entitlement key ids shown in private data.
constexpr uint32_t kMaxNumOfEntitlementKeyIds = 2;
// Entitlement key id length is fixed to 16 bytes.
constexpr uint16_t kEntitlementKeyIdLength = 16;
} // namespace
Status WvCasCaDescriptor::GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids,
const std::vector<std::string>& group_ids,
std::string* serialized_ca_desc) const {
if (serialized_ca_desc == nullptr) {
return {error::INVALID_ARGUMENT,
@@ -69,25 +65,11 @@ Status WvCasCaDescriptor::GenerateCaDescriptor(
if (ca_pid < kMinValidPID || ca_pid > kMaxValidPID) {
return {error::INVALID_ARGUMENT, "PID value is out of the valid range."};
}
if (entitlement_key_ids.size() > kMaxNumOfEntitlementKeyIds) {
return {error::INVALID_ARGUMENT,
absl::StrCat("Number of entitlement key ids shouldn't exceed ",
kMaxNumOfEntitlementKeyIds)};
}
for (const auto& entitlement_key_id : entitlement_key_ids) {
if (entitlement_key_id.size() != kEntitlementKeyIdLength) {
return {error::INVALID_ARGUMENT,
absl::StrCat("Entitlement key id length must be ",
kEntitlementKeyIdLength,
". The offending key id is ", entitlement_key_id)};
}
}
std::string private_data = "";
std::string private_data;
// Field of Entitlement_key_ids could be empty.
if (!provider.empty() && !content_id.empty()) {
private_data =
GeneratePrivateData(provider, content_id, entitlement_key_ids);
private_data = GeneratePrivateData(provider, content_id, group_ids);
}
const size_t descriptor_length =
@@ -130,12 +112,12 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const {
std::string WvCasCaDescriptor::GeneratePrivateData(
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const {
const std::vector<std::string>& group_ids) const {
CaDescriptorPrivateData private_data;
private_data.set_provider(provider);
private_data.set_content_id(content_id);
for (const auto& entitlement_key_id : entitlement_key_ids) {
private_data.add_entitlement_key_ids(entitlement_key_id);
for (const auto& group_id : group_ids) {
private_data.add_group_ids(group_id);
}
return private_data.SerializeAsString();
}

View File

@@ -50,9 +50,7 @@ class WvCasCaDescriptor {
// |ca_pid| the 13-bit PID of the ECMs
// |provider| provider name, put in private data for client to construct pssh
// |content_id| content ID, put in private data for client to construct pssh
// |entitlement_key_ids| entitlement key ids, put in private data for client
// to select entitlement keys from single fat license. This field is only used
// when client uses single fat license.
// |group_ids| the groups ids this channel belongs to. Optional.
// |serialized_ca_desc| a std::string object to receive the encoded descriptor.
//
// Notes:
@@ -60,11 +58,11 @@ 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 Status GenerateCaDescriptor(
uint16_t ca_pid, const std::string& provider,
const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids,
std::string* serialized_ca_desc) const;
virtual Status GenerateCaDescriptor(uint16_t ca_pid,
const std::string& provider,
const std::string& content_id,
const std::vector<std::string>& group_ids,
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
@@ -74,7 +72,7 @@ class WvCasCaDescriptor {
// Return private data in the CA descriptor.
virtual std::string GeneratePrivateData(
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const;
const std::vector<std::string>& group_ids) const;
};
} // namespace cas

View File

@@ -9,6 +9,8 @@
#include "media_cas_packager_sdk/public/wv_cas_ca_descriptor.h"
#include <cstdint>
#include <string>
#include <vector>
#include "testing/gmock.h"
#include "testing/gunit.h"
@@ -20,14 +22,10 @@ namespace widevine {
namespace cas {
namespace {
// Random value for PID
constexpr int kTestPid = 50;
constexpr char kProvider[] = "widevine_test";
constexpr char kContentId[] = "1234";
const std::vector<std::string>* const kEntitlementKeyIds =
new std::vector<std::string>({"fakekey1fakekey1", "fakekey2fakekey2"});
} // namespace
class WvCasCaDescriptorTest : public Test {
@@ -35,7 +33,6 @@ class WvCasCaDescriptorTest : public Test {
WvCasCaDescriptorTest() {}
WvCasCaDescriptor ca_descriptor_;
std::string actual_ca_descriptor_;
std::vector<std::string> entitlement_key_ids_;
};
TEST_F(WvCasCaDescriptorTest, BaseSize) {
@@ -44,7 +41,7 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) {
TEST_F(WvCasCaDescriptorTest, BasicGoodGen) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
kTestPid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -55,7 +52,7 @@ TEST_F(WvCasCaDescriptorTest, NoReturnStringFail) {
ca_descriptor_
.GenerateCaDescriptor(
kTestPid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, /*serialized_ca_desc=*/nullptr)
/*group_ids=*/{}, /*serialized_ca_desc=*/nullptr)
.error_code());
}
@@ -65,14 +62,14 @@ TEST_F(WvCasCaDescriptorTest, PidTooLowFail) {
error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"",
entitlement_key_ids_, &actual_ca_descriptor_)
/*group_ids=*/{}, &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidMinOK) {
const uint32_t min_pid = 0x10;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
min_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
min_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -81,7 +78,7 @@ TEST_F(WvCasCaDescriptorTest, PidMinOK) {
TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
const uint32_t max_pid = 0x1FFE;
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
max_pid, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
max_pid, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -89,17 +86,17 @@ TEST_F(WvCasCaDescriptorTest, PidMaxOK) {
TEST_F(WvCasCaDescriptorTest, PidTooHighFail) {
const uint32_t bad_pid = 0x1FFF;
EXPECT_EQ(error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(
bad_pid, /*provider=*/"", /*content_id=*/"",
/*entitlement_key_ids=*/{}, &actual_ca_descriptor_)
.error_code());
EXPECT_EQ(
error::INVALID_ARGUMENT,
ca_descriptor_
.GenerateCaDescriptor(bad_pid, /*provider=*/"", /*content_id=*/"",
/*group_ids=*/{}, &actual_ca_descriptor_)
.error_code());
}
TEST_F(WvCasCaDescriptorTest, PidOneByte) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
255, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
255, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -107,7 +104,7 @@ TEST_F(WvCasCaDescriptorTest, PidOneByte) {
TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1F00, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x1F00, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -115,7 +112,7 @@ TEST_F(WvCasCaDescriptorTest, PidSecondByte) {
TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0xFFF, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0xFFF, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff");
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -123,7 +120,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelveBits) {
TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x1000, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x1000, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -131,7 +128,7 @@ TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) {
TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x800, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x800, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -139,7 +136,7 @@ TEST_F(WvCasCaDescriptorTest, PidTwelthBit) {
TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x400, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x400, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -147,7 +144,7 @@ TEST_F(WvCasCaDescriptorTest, PidElevenththBit) {
TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x200, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x200, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -155,7 +152,7 @@ TEST_F(WvCasCaDescriptorTest, PidTenthBit) {
TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
0x100, /*provider=*/"", /*content_id=*/"", /*entitlement_key_ids=*/{},
0x100, /*provider=*/"", /*content_id=*/"", /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
@@ -163,14 +160,14 @@ TEST_F(WvCasCaDescriptorTest, PidNinthBit) {
TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoContentIdIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, "", entitlement_key_ids_, &actual_ca_descriptor_));
kTestPid, kProvider, "", /*group_ids=*/{}, &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, PrivateDataWithNoProviderIgnored) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, "", kContentId, entitlement_key_ids_, &actual_ca_descriptor_));
kTestPid, "", kContentId, /*group_ids=*/{}, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
@@ -186,39 +183,16 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataWithNoEntitlementKeyIds) {
actual_ca_descriptor_);
}
TEST_F(WvCasCaDescriptorTest,
PrivateDataFailedWhenNumberOfEntitlementKeyIdsExceedLimit) {
const std::vector<std::string> entitlement_key_ids = {
"fakekey1fakekey1", "fakekey2fakekey2", "fakekey3fakekey3"};
Status status = {error::INVALID_ARGUMENT,
"Number of entitlement key ids shouldn't exceed 2"};
EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, entitlement_key_ids,
&actual_ca_descriptor_));
}
TEST_F(WvCasCaDescriptorTest,
PrivateDataFailedWhenEntitlementKeyIdLengthExceedLimit) {
const std::vector<std::string> entitlement_key_ids = {
"fakekey1fakekey1", "fakekey2fakekey2fakekey2"};
Status status = {error::INVALID_ARGUMENT,
"Entitlement key id length must be 16. The offending key id "
"is fakekey2fakekey2fakekey2"};
EXPECT_EQ(status, ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, entitlement_key_ids,
&actual_ca_descriptor_));
}
TEST_F(WvCasCaDescriptorTest, PrivateData) {
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
*kEntitlementKeyIds,
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x3d\x4a\xd4\xe0\x32", 6);
std::vector<std::string> group_ids = {"group1", "group2"};
EXPECT_OK(ca_descriptor_.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, group_ids, &actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x29\x4a\xd4\xe0\x32", 6);
CaDescriptorPrivateData private_data;
private_data.set_provider(kProvider);
private_data.set_content_id(kContentId);
for (const auto& entitlementKeyId : *kEntitlementKeyIds) {
private_data.add_entitlement_key_ids(entitlementKeyId);
for (const auto& group_id : group_ids) {
private_data.add_group_ids(group_id);
}
EXPECT_EQ(expected_ca_descriptor + private_data.SerializeAsString(),
actual_ca_descriptor_);
@@ -232,7 +206,7 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
std::string GeneratePrivateData(
const std::string& provider, const std::string& content_id,
const std::vector<std::string>& entitlement_key_ids) const override {
const std::vector<std::string>& group_ids) const override {
return private_data_;
}
@@ -243,9 +217,9 @@ class FakePrivateDataCaDescriptor : public WvCasCaDescriptor {
TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) {
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data("X");
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kContentId, /*group_ids=*/{},
&actual_ca_descriptor_));
const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7);
EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_);
}
@@ -254,9 +228,9 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) {
const std::string private_data_bytes("X1234abcde");
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kContentId, /*group_ids=*/{},
&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_);
}
@@ -265,9 +239,9 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) {
const std::string private_data_bytes(251, 'x');
FakePrivateDataCaDescriptor fake_descriptor;
fake_descriptor.set_private_data(private_data_bytes);
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(
kTestPid, kProvider, kContentId, *kEntitlementKeyIds,
&actual_ca_descriptor_));
EXPECT_OK(fake_descriptor.GenerateCaDescriptor(kTestPid, kProvider,
kContentId, /*group_ids=*/{},
&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_);
}
@@ -276,12 +250,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(
error::INVALID_ARGUMENT,
fake_descriptor
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
*kEntitlementKeyIds, &actual_ca_descriptor_)
.error_code());
EXPECT_EQ(error::INVALID_ARGUMENT,
fake_descriptor
.GenerateCaDescriptor(kTestPid, kProvider, kContentId,
/*group_ids=*/{}, &actual_ca_descriptor_)
.error_code());
}
} // namespace cas

View File

@@ -41,6 +41,7 @@ EcmInitParameters ConvertToEcmInitParameters(
init_params.cas_id = ecm_parameters.cas_id;
init_params.ecm_version = ecm_parameters.ecm_version;
init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key;
init_params.entitlement_rotation = ecm_parameters.entitlement_rotation;
return init_params;
}
@@ -67,6 +68,16 @@ void WvCasEcm::SetServiceBlocking(
ecm_->SetServiceBlocking(service_blocking);
}
Status WvCasEcm::SetEntitlementRotationWindowLeft(
uint32_t entitlement_rotation_window_left) {
return ecm_->SetEntitlementRotationWindowLeft(
entitlement_rotation_window_left);
}
uint32_t WvCasEcm::GetEntitlementRotationWindowLeft() const {
return ecm_->GetEntitlementRotationWindowLeft();
}
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,

View File

@@ -61,6 +61,7 @@ struct WvCasEcmParameters {
uint16_t cas_id = 0x4AD4;
EcmVersion ecm_version = EcmVersion::kV2;
std::string ecc_private_signing_key;
EntitlementKeyRotationInfo entitlement_rotation;
};
// Class for generating Widevine CAS ECMs.
@@ -94,6 +95,20 @@ class WvCasEcm {
virtual void SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking);
// Sets the current value of the entitlement key rotation window left to
// |entitlement_rotation_window_left|. The value will be used in
// GenerateEcm/GenerateSingleKeyEcm if entitlement rotation is enabled as
// specified at initializing, and is automatically decreased by 1 on each call
// to GenerateEcm/GenerateSingleKeyEcm, until it reaches 1.
virtual Status SetEntitlementRotationWindowLeft(
uint32_t entitlement_rotation_window_left);
// Gets the current value of the entitlement key rotation window left. The
// value is used in GenerateEcm/GenerateSingleKeyEcm, and is automatically
// decreased by 1 on each call to GenerateEcm/GenerateSingleKeyEcm, until it
// reaches 1.
virtual uint32_t GetEntitlementRotationWindowLeft() const;
// Constructs a Widevine ECM using the provided key info.
// Args:
// |even_key| information for even key to be encoded into ECM.
@@ -106,6 +121,9 @@ class WvCasEcm {
// consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate
// entitlement key.
// If entitlement rotation is enabled as specified at initializing, the
// entitlement key rotation window left value will be automatically decreased
// by 1, until it reaches 1.
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,
@@ -122,6 +140,9 @@ class WvCasEcm {
// |serialized_ecm| caller-supplied std::string pointer to receive the ECM.
// The |key| contents (specifically IV sizes) must be consistent
// with the initialized settings.
// If entitlement rotation is enabled as specified at initializing, the
// entitlement key rotation window left value will be automatically decreased
// by 1, until it reaches 1.
virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type,
const std::vector<std::string>& group_ids,

View File

@@ -11,6 +11,7 @@
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
@@ -81,7 +82,8 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
return {error::INVALID_ARGUMENT, error_message};
}
processed_request_length = inner_handler_->HandleRequest(
request_buffer, response_buffer, &response_length);
request_buffer, absl::MakeSpan(response_buffer, response_buffer_size),
&response_length);
if (processed_request_length == 0) {
return {error::INTERNAL, "request not processed."};
}
@@ -89,5 +91,14 @@ Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
return OkStatus();
}
WvEcmgChannelStatus WvCasEcmgClientHandler::GetChannelStatus() const {
return inner_handler_->GetChannelStatus();
}
std::vector<WvEcmgStreamStatus> WvCasEcmgClientHandler::GetStreamStatus()
const {
return inner_handler_->GetStreamStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -74,6 +74,12 @@ class WvCasEcmgClientHandler {
size_t& response_length,
size_t& processed_request_length);
// Retrieves the current channel status;
WvEcmgChannelStatus GetChannelStatus() const;
// Retrieves the status of all open streams;
std::vector<WvEcmgStreamStatus> GetStreamStatus() const;
protected:
// For unit test only.
explicit WvCasEcmgClientHandler(

View File

@@ -11,6 +11,7 @@
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/memory/memory.h"
#include "absl/types/span.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecmg_client_handler.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
@@ -25,7 +26,7 @@ class MockEcmgClientHandler : public EcmgClientHandler {
public:
MockEcmgClientHandler() : EcmgClientHandler(&config_) {}
MOCK_METHOD(size_t, HandleRequest,
(const char* const request, char* response,
(const char* const request, const absl::Span<char> response,
size_t* response_length),
(override));
@@ -50,7 +51,9 @@ TEST(WvCasEcmgClientHandlerTest, HandleRequestSuccess) {
size_t processed_length = 0;
auto mock_internal_handler = absl::make_unique<MockEcmgClientHandler>();
EXPECT_CALL(*mock_internal_handler,
HandleRequest(request_buffer, response_buffer, &response_length))
HandleRequest(request_buffer,
absl::MakeSpan(response_buffer, kBufferSize),
&response_length))
.WillOnce(testing::DoAll(testing::Return(100)));
auto handler = absl::make_unique<TestWvCasEcmgClientHandler>(

View File

@@ -26,7 +26,6 @@ using ::testing::NotNull;
using ::testing::Pointwise;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::WithArg;
namespace widevine {
@@ -62,12 +61,11 @@ class MockEmm : public Emm {
Status, SetServiceBlocking,
(const std::vector<ServiceBlockingInitParameters>& service_blockings),
(override));
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm),
(const, override));
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm), (override));
MOCK_METHOD(Status, GenerateEmmTsPackets,
(uint16_t pid, uint8_t* continuity_counter,
const absl::Span<uint8_t> packet, ssize_t* bytes_modified),
(const, override));
(override));
};
class MockWvCasEmm : public WvCasEmm {

View File

@@ -39,8 +39,9 @@ WvCasKeyFetcher::WvCasKeyFetcher(const std::string& signing_provider,
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_id.empty() && request_params.group_id.empty()) {
return {error::INVALID_ARGUMENT,
"Either content ID or group ID must be specified."};
}
if (request_params.content_provider.empty()) {
return {error::INVALID_ARGUMENT, "Content Provider is empty."};
@@ -58,16 +59,21 @@ Status WvCasKeyFetcher::CreateEntitlementRequest(
}
CasEncryptionRequest request;
request.set_content_id(request_params.content_id);
request.set_provider(request_params.content_provider);
request.set_key_rotation(request_params.key_rotation);
if (!request_params.group_id.empty()) {
request.set_group_id(request_params.group_id);
} else {
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);
}
if (request_params.entitlement_rotation_enabled) {
request.set_entitlement_period_index(
request_params.entitlement_period_index);
}
std::string request_json;
JsonPrintOptions print_options;
@@ -162,6 +168,9 @@ Status WvCasKeyFetcher::ParseEntitlementResponse(
entitlement->key_id = key.key_id();
entitlement->key_value = key.key();
entitlement->track_type = key.track_type();
if (!response.group_id().empty()) {
entitlement->group_id = response.group_id();
}
// EVEN or SINGLE are both marked as is_even_key.
entitlement->is_even_key =
key.key_slot() != CasEncryptionResponse::KeyInfo::ODD;

View File

@@ -29,12 +29,17 @@ namespace cas {
// |group_id| optional field indicates if this is a key used for a group of
// contents. If this field is not empty, entitlement key would be generated
// for the group instead of the single content.
// |entitlement_rotation_enabled| Whether entitlement rotation is enabled.
// |entitlement_period_index| The requested entitlement period index if
// entitlement rotation is enabled.
struct EntitlementRequestParams {
std::string content_id;
std::string content_provider;
std::vector<std::string> track_types;
bool key_rotation;
std::string group_id;
bool entitlement_rotation_enabled = false;
uint32_t entitlement_period_index;
};
// WV CAS KeyFetcher. Performs the communication with the Widevine License

View File

@@ -8,6 +8,8 @@
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include <string>
#include "glog/logging.h"
#include "google/protobuf/util/json_util.h"
#include "testing/gmock.h"
@@ -38,12 +40,12 @@ const char kSignedCasEncryptionRequest[] =
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
"test\"}";
absl::string_view kSignedCasEncryptionRequestForGroupKey =
constexpr absl::string_view kSignedCasEncryptionRequestForGroupKey =
"{\"request\":"
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsInRy"
"YWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2UsImdyb3VwX2lkIjoiWjNKdm"
"RYQXgifQ==\",\"signature\":\"iDaAr74ldEV6X1X9kwyLoZ/"
"hfP5RJOkXEzucq6IXyfQ=\",\"signer\":\"widevine_test\"}";
"\"eyJwcm92aWRlciI6IndpZGV2aW5lIiwidHJhY2tfdHlwZXMiOlsiU0QiXSwia2V5X3JvdG"
"F0aW9uIjpmYWxzZSwiZ3JvdXBfaWQiOiJaM0p2ZFhBeCJ9\",\"signature\":"
"\"vRZ2qt65UC171S9pVEBib2KyTAbTGO+AjsCgV4d+4a0=\",\"signer\":\"widevine_"
"test\"}";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
@@ -57,6 +59,8 @@ const char kContentId[] = "21140844";
const char kGroupId[] = "group1";
const char kEntitlementKeyId[] = "fake_key_id.....";
const char kEntitlementKey[] = "fakefakefakefakefakefakefakefake";
const uint32_t kEntitlementPeriod = 123;
const char kEntitlementKeyIdForEntitlementPeriod123[] = "1234567812345678";
} // namespace
namespace widevine {
@@ -100,10 +104,11 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
return Status(error::INTERNAL);
}
// Validate the parameters shown in the request.
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider);
if (request.has_group_id()) {
EXPECT_EQ(request.group_id(), kGroupId);
} else {
EXPECT_EQ(request.content_id(), kContentId);
}
CasEncryptionResponse response;
@@ -119,13 +124,19 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher {
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id(kEntitlementKeyId);
key->set_key_id(request.entitlement_period_index() ==
kEntitlementPeriod
? kEntitlementKeyIdForEntitlementPeriod123
: kEntitlementKeyId);
key->set_key(kEntitlementKey);
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(kEntitlementKeyId);
key->set_key_id(request.entitlement_period_index() ==
kEntitlementPeriod
? kEntitlementKeyIdForEntitlementPeriod123
: kEntitlementKeyId);
key->set_key(kEntitlementKey);
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD);
@@ -212,6 +223,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
&expected_key_value));
EXPECT_EQ(entitlement.key_value, expected_key_value);
EXPECT_TRUE(entitlement.group_id.empty());
}
TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
@@ -230,7 +242,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
CasEncryptionRequest request;
ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_request.request(), &request));
EXPECT_EQ(request.content_id(), kContentId);
EXPECT_EQ(request.provider(), kProvider);
EXPECT_EQ(request.group_id(), kGroupId);
EXPECT_EQ(request.track_types_size(), 1);
@@ -247,7 +258,6 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
CasEncryptionResponse response;
ASSERT_OK(
google::protobuf::util::JsonStringToMessage(signed_response.response(), &response));
EXPECT_EQ(response.content_id(), kContentId);
EXPECT_EQ(response.status(), CasEncryptionResponse::OK);
EXPECT_EQ(response.group_id(), kGroupId);
@@ -261,6 +271,7 @@ TEST_F(WvCasKeyFetcherTest, TestRequestWithGroupId) {
EXPECT_EQ(entitlement.is_even_key, true);
EXPECT_EQ(entitlement.key_id, kEntitlementKeyId);
EXPECT_EQ(entitlement.key_value, kEntitlementKey);
EXPECT_EQ(entitlement.group_id, kGroupId);
}
TEST_F(WvCasKeyFetcherTest, OneKeyOK) {
@@ -348,5 +359,27 @@ TEST_F(WvCasKeyFetcherTest, BadResponseFail) {
.error_code());
}
TEST_F(WvCasKeyFetcherTest, EntitlementRotationSuccess) {
MockWvCasKeyFetcher mock_key_fetcher(kSigningProvider, kSingingKey,
kSingingIv);
std::string request;
EntitlementRequestParams request_params =
CreateRequestParamsStruct(kContentId, kProvider, /*group_id=*/"",
{kTrackTypeSD}, /*key_rotation=*/true);
request_params.entitlement_rotation_enabled = true;
request_params.entitlement_period_index = kEntitlementPeriod;
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(), 2);
EXPECT_EQ(entitlements[0].key_id, kEntitlementKeyIdForEntitlementPeriod123);
EXPECT_EQ(entitlements[1].key_id, kEntitlementKeyIdForEntitlementPeriod123);
}
} // namespace cas
} // namespace widevine

View File

@@ -86,6 +86,23 @@ struct EntitlementKeyInfo {
enum class EcmVersion : int { kV2 = 0, kV3 = 1 };
// Used for generating ECMs if entitlement key rotation is enabled.
struct EntitlementKeyRotationInfo {
// Indicates if entitlement key rotation feature is used. If enabled,
// |period_index| and |rotation_window_left| must also be specified and will
// be carried in generated ECMs.
bool rotation_enabled = false;
// The current entitlement key period index. Only used if |rotation_enabled|
// is set to true.
uint32_t period_index = 0;
// It tells the client how many crypto periods (unique ECMs) left (including
// this ECM) that a new license request must be made. Must be a positive
// value. Only used if |rotation_enabled| is set to true.
// The value will automatically decrease by 1 on each call to GenerateEcm or
// GenerateSingleKeyEcm, until the value reaches 1.
uint32_t rotation_window_left = 0;
};
// A struct that captures the Simulcrypt ECMG configurations. Most fields are
// Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10)
// Section 5.3).
@@ -177,6 +194,8 @@ struct EcmgCustomParameters {
// when the ECM is received, and stops util the device is no longer in
// |device_groups|.
std::vector<std::string> service_blocking_groups;
// Used if entitlement key rotation is enabled.
EntitlementKeyRotationInfo entitlement_rotation;
};
// A custom access control processing function used by ECMG to get information
@@ -233,6 +252,33 @@ typedef std::function<EcmServiceBlockingParams(uint16_t channel_id,
uint16_t stream_id)>
ServiceBlockingSettingFunc;
struct WvEcmgChannelStatus {
// If the channel has been set up or not.
bool has_setup;
uint16_t channel_id;
uint16_t ca_system_id;
int num_of_open_streams;
EcmVersion ecm_version;
// Below are Simulcrypt parameters values (see ETSI TS 103 197 V1.5.1
// (2008-10) Section 5.3).
uint8_t section_tspkt_flag;
uint16_t delay_start;
uint16_t delay_stop;
uint16_t ecm_pep_period;
uint16_t max_streams;
uint16_t min_cp_duration;
uint8_t lead_cw;
uint8_t cw_per_message;
uint16_t max_comp_time;
};
struct WvEcmgStreamStatus {
uint16_t stream_id;
uint16_t ecm_id;
CryptoMode crypto_mode;
int age_restriction;
};
struct WvCasEncryptionRequest {
std::string content_id;
std::string provider;

View File

@@ -26,10 +26,6 @@
#include "media_cas_packager_sdk/internal/emmg.h"
namespace {
// Maximum number of entitlement key ids shown in private data.
constexpr uint16_t kMaxNumOfEntitlementKeyIds = 2;
// Entitlement key id length is fixed to 16 bytes.
constexpr uint16_t kEntitlementKeyIdLengthBytes = 16;
constexpr int32_t kEmmDataType = 0;
constexpr int32_t kPrivateDataType = 1;
} // namespace
@@ -58,8 +54,8 @@ ABSL_FLAG(std::string, content_provider, "",
"Content provider to put into priavte data.");
ABSL_FLAG(std::string, content_id, "", "Content id to put into priavte data");
ABSL_FLAG(
std::vector<std::string>, entitlement_key_ids, {},
"Comma-separated list of entitlement_key_ids to put into private data");
std::vector<std::string>, group_ids, {},
"Comma-separated list of group_ids to put into private data. Optional.");
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");
@@ -93,8 +89,8 @@ void CheckEmmgUsage(const widevine::cas::EmmgConfig& config) {
"data).";
LOG_IF(WARNING, !config.content_id.empty())
<< "content_id is set but ignored as data_type is set to 0 (EMM data).";
LOG_IF(WARNING, !config.entitlement_key_ids.empty())
<< "entitlement_key_ids is set but ignored as data_type is set to 0 "
LOG_IF(WARNING, !config.group_ids.empty())
<< "group_ids is set but ignored as data_type is set to 0 "
"(EMM data).";
}
@@ -130,17 +126,7 @@ void BuildEmmgConfig(widevine::cas::EmmgConfig* config) {
// Below are private data specific configurations.
config->content_provider = absl::GetFlag(FLAGS_content_provider);
config->content_id = absl::GetFlag(FLAGS_content_id);
// Check and set entitlement_key_ids.
config->entitlement_key_ids = absl::GetFlag(FLAGS_entitlement_key_ids);
CHECK_LE(config->entitlement_key_ids.size(), kMaxNumOfEntitlementKeyIds)
<< absl::StrCat("Number of entitlement key ids shouldn't exceed ",
kMaxNumOfEntitlementKeyIds);
for (const auto& entitlement_key_id : config->entitlement_key_ids) {
CHECK_EQ(entitlement_key_id.size(), kEntitlementKeyIdLengthBytes)
<< absl::StrCat("Entitlement key id length must be ",
kEntitlementKeyIdLengthBytes,
". The offending key id is ", entitlement_key_id);
}
config->group_ids = absl::GetFlag(FLAGS_group_ids);
// Below are EMM data specific configurations.
config->ecc_signing_key = absl::GetFlag(FLAGS_ecc_signing_key);