Add support for Widevine ECM v3

Widevine ECM v3 is redesigned mainly based on protobuf, and supports new features including carrying fingerprinting and service blocking information. Existing clients must upgrade the Widevine CAS plugin to use the new ECM v3.
This commit is contained in:
Lu Chen
2020-12-14 09:49:52 -08:00
parent ad81d517a5
commit 79e39b482d
46 changed files with 3096 additions and 1035 deletions

View File

@@ -90,6 +90,7 @@ cc_library(
":wv_cas_types",
"//base",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/types:span",
"//common:status",
"//media_cas_packager_sdk/internal:ecm",
],
@@ -184,6 +185,7 @@ cc_library(
deps = [
"//base",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/types:span",
"//common:status",
"//media_cas_packager_sdk/internal:emm",
],

View File

@@ -12,6 +12,8 @@
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "absl/types/span.h"
#include "common/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
namespace widevine {
@@ -35,6 +37,10 @@ EcmInitParameters ConvertToEcmInitParameters(
init_params.key_rotation_enabled = ecm_parameters.key_rotation_enabled;
init_params.crypto_mode = ecm_parameters.crypto_mode;
init_params.age_restriction = ecm_parameters.age_restriction;
init_params.cas_id = ecm_parameters.cas_id;
init_params.ecm_version = ecm_parameters.ecm_version;
init_params.ecc_private_signing_key = ecm_parameters.ecc_private_signing_key;
return init_params;
}
} // namespace
@@ -42,25 +48,28 @@ EcmInitParameters ConvertToEcmInitParameters(
WvCasEcm::WvCasEcm(
const WvCasEcmParameters& ecm_parameters,
const std::vector<EntitlementKeyInfo>& injected_entitlements) {
CHECK(!injected_entitlements.empty());
ecm_param_ = ecm_parameters;
injected_entitlements_.assign(injected_entitlements.begin(),
injected_entitlements.end());
ecm_ = absl::make_unique<Ecm>();
Status status = ecm_->Initialize(ConvertToEcmInitParameters(ecm_parameters),
injected_entitlements);
CHECK(status.ok()) << "Failed to get initialize ECM class." << status;
}
WvCasEcm::~WvCasEcm() = default;
void WvCasEcm::SetFingerprinting(
const EcmFingerprintingParams* fingerprinting) {
ecm_->SetFingerprinting(fingerprinting);
}
void WvCasEcm::SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking) {
ecm_->SetServiceBlocking(service_blocking);
}
Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,
std::string* serialized_ecm) const {
// Create an instance of Ecm in order to set the entitlement keys.
auto cas_ecm = absl::make_unique<Ecm>();
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
if (!status.ok()) {
LOG(ERROR) << "Failed to get initialize ECM class." << status;
return status;
}
EntitledKeyInfo entitled_even_key = ConvertToEntitledKeyInfo(even_key);
EntitledKeyInfo entitled_odd_key = ConvertToEntitledKeyInfo(odd_key);
// Make content key to 16 bytes if crypto mode is Csa2.
@@ -74,22 +83,13 @@ Status WvCasEcm::GenerateEcm(const WvCasContentKeyInfo& even_key,
entitled_odd_key.key_value + entitled_odd_key.key_value;
}
}
return cas_ecm->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
serialized_ecm);
return ecm_->GenerateEcm(&entitled_even_key, &entitled_odd_key, track_type,
serialized_ecm);
}
Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
const std::string& track_type,
std::string* serialized_ecm) const {
// Create an instance of Ecm in order to set the entitlement keys.
auto cas_ecm = absl::make_unique<Ecm>();
EcmInitParameters init_params = ConvertToEcmInitParameters(ecm_param_);
Status status = cas_ecm->Initialize(init_params, injected_entitlements_);
if (!status.ok()) {
LOG(ERROR) << "Failed to get initialize ECM class." << status;
return status;
}
EntitledKeyInfo entitled_key = ConvertToEntitledKeyInfo(key);
// Make content key to 16 bytes if crypto mode is Csa2.
if (ecm_param_.crypto_mode == CryptoMode::kDvbCsa2) {
@@ -97,14 +97,27 @@ Status WvCasEcm::GenerateSingleKeyEcm(const WvCasContentKeyInfo& key,
entitled_key.key_value = entitled_key.key_value + entitled_key.key_value;
}
}
return cas_ecm->GenerateSingleKeyEcm(&entitled_key, track_type,
serialized_ecm);
return ecm_->GenerateSingleKeyEcm(&entitled_key, track_type, serialized_ecm);
}
Status WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet) {
return Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter, packet);
uint8_t* packet, ssize_t* packet_size) {
if (continuity_counter == nullptr || packet == nullptr ||
packet_size == nullptr) {
return {error::INVALID_ARGUMENT,
"continuity_counter, packet and packet_size must not be null"};
}
ssize_t bytes_modified = 0;
Status status = Ecm::GenerateTsPacket(ecm, pid, table_id, continuity_counter,
absl::MakeSpan(packet, *packet_size),
&bytes_modified);
if (!status.ok()) {
return status;
}
*packet_size = bytes_modified;
return OkStatus();
}
} // namespace cas

View File

@@ -9,6 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <memory>
#include <string>
#include <vector>
@@ -19,6 +20,8 @@
namespace widevine {
namespace cas {
class Ecm;
// Information needed to generate content key portion of ECM.
// Fields:
// |key_id| key ID for the content key, must be 16 bytes.
@@ -44,11 +47,19 @@ struct WvCasContentKeyInfo {
// |crypto_mode| the encryption mode used for the content stream.
// A constant of type CryptoMode.
// |age_restriction| minimum age required; the value represents actual age.
// |cas_id| CA system id that is in the ECM. Must be 0x4AD4 or 0x56C0~0x56C9
// (all inclusive).
// |ecm_version| version of generated ECM.
// |ecc_private_signing_key| Private signing key used to sign ECM data. Must
// be an elliptic-curve cryptography key.
struct WvCasEcmParameters {
EcmIvSize content_iv_size = kIvSize8;
bool key_rotation_enabled = true;
CryptoMode crypto_mode = CryptoMode::kAesCtr;
uint8_t age_restriction = 0;
uint16_t cas_id = 0x4AD4;
EcmVersion ecm_version = EcmVersion::kV2;
std::string ecc_private_signing_key;
};
// Class for generating Widevine CAS ECMs.
@@ -66,10 +77,23 @@ class WvCasEcm {
const std::vector<EntitlementKeyInfo>& injected_entitlements);
WvCasEcm(const WvCasEcm&) = delete;
WvCasEcm& operator=(const WvCasEcm&) = delete;
virtual ~WvCasEcm() = default;
virtual ~WvCasEcm();
// Accept keys and IVs and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes).
// Set fingerprinting info that will be embedded into the generated ECM. The
// configuration will be used in all following ECMs generated by calling
// GenerateEcm() or GenerateSingleKeyEcm().
// |fingerprinting| may be set to nullptr to clear the fingerprinting info.
virtual void SetFingerprinting(const EcmFingerprintingParams* fingerprinting);
// Set service blocking info that will be embedded into the generated ECM. The
// configuration will be used in all following ECMs generated by calling
// GenerateEcm() or GenerateSingleKeyEcm().
// |service_blocking| may be set to nullptr to clear the service blocking
// info.
virtual void SetServiceBlocking(
const EcmServiceBlockingParams* service_blocking);
// Constructs a Widevine ECM using the provided key info.
// Args:
// |even_key| information for even key to be encoded into ECM.
// |odd_key| information for odd key to be encoded into ECM.
@@ -78,15 +102,14 @@ class WvCasEcm {
// The |even_key| and |odd_key| contents (specifically IV sizes) must be
// consistent with the initialized settings.
// The even_key and odd_key will be wrapped using the appropriate
// entitlement key. Wrapping modifies the original structure.
// entitlement key.
virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key,
const WvCasContentKeyInfo& odd_key,
const std::string& track_type,
std::string* serialized_ecm) const;
// Accept a key and IV and construct an ECM that will fit into a Transport
// Stream packet payload (184 bytes). This call is specifically for the case
// where key rotation is disabled.
// Constructs a Widevine ECM using the provided key info. This call is
// specifically for the case where key rotation is disabled.
// Args:
// |key| information for key to be encoded into ECM.
// |track_type| the track that the key is being used to encrypt.
@@ -112,16 +135,18 @@ class WvCasEcm {
// it will be incremented, only last 4 bits are used
// - |packet| a buffer of size at least 188 bytes to be used to return
// the generated TS packet
// - |packet_size| is the size of the allocated |packet|. It will be updated
// as the number of bytes actually used.
//
// Returns:
// - A status indicating whether there was any error during processing
static Status GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id, uint8_t* continuity_counter,
uint8_t* packet);
uint8_t* packet, ssize_t* packet_size);
private:
std::unique_ptr<Ecm> ecm_;
WvCasEcmParameters ecm_param_;
std::vector<EntitlementKeyInfo> injected_entitlements_;
};
} // namespace cas

View File

@@ -36,6 +36,21 @@ void WvCasEcmgClientHandler::SetCustomEntitlementKeyFetcherFunc(
return inner_handler_->SetCustomEntitlementKeyFetcherFunc(fetcher);
}
void WvCasEcmgClientHandler::SetCustomAccessCriteriaProcessFunc(
CustomAccessCriteriaProcessFunc ac_processor) {
return inner_handler_->SetCustomAccessCriteriaProcessFunc(ac_processor);
}
void WvCasEcmgClientHandler::SetFingerprintingSettingFunc(
FingerprintingSettingFunc fingerprinting_func) {
return inner_handler_->SetFingerprintingSettingFunc(fingerprinting_func);
}
void WvCasEcmgClientHandler::SetServiceBlockingSettingFunc(
ServiceBlockingSettingFunc service_blocking_func) {
return inner_handler_->SetServiceBlockingSettingFunc(service_blocking_func);
}
Status WvCasEcmgClientHandler::HandleRequest(size_t request_buffer_size,
const char* const request_buffer,
size_t response_buffer_size,

View File

@@ -36,6 +36,27 @@ class WvCasEcmgClientHandler {
// Calling this function is optional.
void SetCustomEntitlementKeyFetcherFunc(EntitlementKeyFetcherFunc fetcher);
// Sets the custom access criteria processing function used by ECMG to get
// information including entitlement keys, content iv, crypto mode, etc.
// Calling this function is optional: If the function is set, access criteria
// received in the CwProvision message from SCS will be processed by this
// callback function (SDK will no longer process the access criteria). If not
// set, access criteria will be processed by the SDK.
void SetCustomAccessCriteriaProcessFunc(
CustomAccessCriteriaProcessFunc ac_processor);
// Sets the fingerprinting setting function, which will be called upon each
// CwProvisioning request. It controls the fingerprinting information carried
// in ECMs.
void SetFingerprintingSettingFunc(
FingerprintingSettingFunc fingerprinting_func);
// Sets the service blocking setting function, which will be called upon each
// CwProvisioning request. It controls the service blocking information
// carried in ECMs.
void SetServiceBlockingSettingFunc(
ServiceBlockingSettingFunc service_blocking_func);
// Handles a |request| from the SCS client. If any response is generated, it
// will return the response via |response_buffer| and |response_length|.
// Args:

View File

@@ -10,6 +10,7 @@
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "absl/types/span.h"
#include "media_cas_packager_sdk/internal/emm.h"
namespace widevine {
@@ -93,8 +94,16 @@ Status WvCasEmm::GenerateEmmTsPackets(uint16_t pid, uint8_t* continuity_counter,
return {error::INVALID_ARGUMENT,
"continuity_counter, packet and packet_size must not be null"};
}
return emm_->GenerateEmmTsPackets(pid, continuity_counter, packet,
packet_size);
ssize_t bytes_modified = 0;
Status status = emm_->GenerateEmmTsPackets(
pid, continuity_counter, absl::MakeSpan(packet, *packet_size),
&bytes_modified);
if (!status.ok()) {
return status;
}
*packet_size = bytes_modified;
return OkStatus();
}
} // namespace cas

View File

@@ -16,6 +16,7 @@
#include "media_cas_packager_sdk/internal/emm.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAreArray;
using ::testing::IsEmpty;
@@ -25,6 +26,7 @@ using ::testing::Pointwise;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::WithArg;
namespace widevine {
namespace cas {
@@ -62,8 +64,8 @@ class MockEmm : public Emm {
MOCK_METHOD(Status, GenerateEmm, (std::string * serialized_emm),
(const, override));
MOCK_METHOD(Status, GenerateEmmTsPackets,
(uint16_t pid, uint8_t* continuity_counter, uint8_t* packet,
ssize_t* packet_size),
(uint16_t pid, uint8_t* continuity_counter,
const absl::Span<uint8_t> packet, ssize_t* bytes_modified),
(const, override));
};
@@ -204,12 +206,13 @@ TEST_F(WvCasEmmTest, GenerateEmmTsPacketsSuccess) {
uint8_t expected_continuity_counter = 2;
uint8_t expected_packet[] = {1, 2, 3, 4, 5};
EXPECT_CALL(*mock_internal_emm_,
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
.WillOnce(
DoAll(SetArgPointee<1>(expected_continuity_counter),
SetArrayArgument<2>(expected_packet,
expected_packet + sizeof(expected_packet)),
SetArgPointee<3>(sizeof(expected_packet)), Return(OkStatus())));
GenerateEmmTsPackets(kTestPid, NotNull(), _, NotNull()))
.WillOnce(DoAll(
SetArgPointee<1>(expected_continuity_counter),
WithArg<2>([&expected_packet](absl::Span<uint8_t> packet) {
memcpy(packet.data(), expected_packet, sizeof(expected_packet));
}),
SetArgPointee<3>(sizeof(expected_packet)), Return(OkStatus())));
uint8_t continuity_counter = 1;
uint8_t packet[sizeof(expected_packet)];
@@ -222,7 +225,7 @@ TEST_F(WvCasEmmTest, GenerateEmmTsPacketsSuccess) {
TEST_F(WvCasEmmTest, GenerateEmmTsPacketsFail) {
EXPECT_CALL(*mock_internal_emm_,
GenerateEmmTsPackets(kTestPid, NotNull(), NotNull(), NotNull()))
GenerateEmmTsPackets(kTestPid, NotNull(), _, NotNull()))
.WillOnce(Return(Status(error::INVALID_ARGUMENT)));
uint8_t continuity_counter = 1;

View File

@@ -9,6 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_
#include <functional>
#include <string>
#include <vector>
@@ -65,6 +66,8 @@ struct EntitlementKeyInfo {
std::string key_value; // must be 32 bytes.
};
enum class EcmVersion : int { kV2 = 0, kV3 = 1 };
// A struct that captures the Simulcrypt ECMG configurations. Most fields are
// Simulcrypt standard fields (See ETSI TS 103 197 V1.5.1 (2008-10)
// Section 5.3).
@@ -109,6 +112,11 @@ struct EcmgConfig {
// will be included in the ECM. If new crypto_mode is received from SCS, ECM
// will use the new one.
CryptoMode crypto_mode;
// Version of generated ECM.
EcmVersion ecm_version = EcmVersion::kV2;
// Private signing key used to sign ECM data. Must be an elliptic-curve
// cryptography key.
std::string ecc_private_signing_key;
};
// A custom entitlement key fetching function used by ECMG to fetch entitlement
@@ -127,6 +135,82 @@ struct EcmgConfig {
using EntitlementKeyFetcherFunc = std::vector<EntitlementKeyInfo> (*)(
uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id);
struct EcmgCustomParameters {
// Minimum age required for the content. The value represents actual age.
// A negative value means the field is not set.
int age_restriction = -1;
// The encryption mode used for the content stream.
std::string crypto_mode;
// Initial vector used in the content encryption, must be 8 (DvbCsa2) or 16
// bytes (all other crypto modes). The size of the vector must not exceed 2.
// The first entry is for the even key slot, the second entry is for the odd
// key slot. It is suggested that the same IV used for both even and odd key
// slot for better decrypting performance.
std::vector<std::string> content_ivs;
// Entitlement keys used to encrypt the content keys (control words).
// The size of the vector must not exceed 2. When the vector size is 2, one of
// them must be specified as even key and the other must be odd key (sequence
// does not matter).
std::vector<EntitlementKeyInfo> entitlement_keys;
// Fingerprinting control that will be embedded in ECM. The control is opaque
// to Widevine.
std::string fingerprinting_control;
// Devices that should have service blocking enforced. The blocking starts
// when the ECM is received, and stops util the device is no longer in
// |device_groups|.
std::vector<std::string> service_blocking_groups;
};
// A custom access control processing function used by ECMG to get information
// including entitlement keys, content iv, crypto mode, etc. If set, the
// function will be called when an access criteria is received in the
// CwProvision message from SCS. The access criteria will no longer be processed
// by the SDK.
// Args:
// |channel_id| is the channel id received at ECMG from SCS when setting up
// the channel.
// |stream_id| is the stream id received at ECMG from SCS when setting up
// the stream.
// |access_criteria| the received access criteria from SCS.
// Returns EcmgCustomParameters. Negative or empty fields will be ignored.
typedef std::function<EcmgCustomParameters(uint16_t channel_id, uint16_t stream_id,
const std::string& access_criteria)>
CustomAccessCriteriaProcessFunc;
struct EcmFingerprintingParams {
// The |control| is opaque to Widevine.
std::string control;
};
// Function that will be called upon each CwProvisioning request. It controls
// the fingerprinting information carried in ECMs.
// Args:
// |channel_id| is the channel id received at ECMG from SCS when setting up
// the channel.
// |stream_id| is the stream id received at ECMG from SCS when setting up
// the stream.
typedef std::function<EcmFingerprintingParams(uint16_t channel_id,
uint16_t stream_id)>
FingerprintingSettingFunc;
struct EcmServiceBlockingParams {
// Devices that should have service blocking enforced. The blocking starts
// when the ECM is received, and stops util the device is no longer in
// |device_groups|.
std::vector<std::string> device_groups;
};
// Function that will be called upon each CwProvisioning request. It controls
// the service blocking information carried in ECMs.
// Args:
// |channel_id| is the channel id received at ECMG from SCS when setting up
// the channel.
// |stream_id| is the stream id received at ECMG from SCS when setting up
// the stream.
typedef std::function<EcmServiceBlockingParams(uint16_t channel_id,
uint16_t stream_id)>
ServiceBlockingSettingFunc;
struct WvCasEncryptionRequest {
std::string content_id;
std::string provider;

View File

@@ -49,6 +49,13 @@ ABSL_FLAG(std::string, crypto_mode, "AesCtr",
"Default encryption mode if not provided in API calls. Choices are "
"\"AesCtr\", \"AesCbc\", \"DvbCsa2\", \"DvbCsa3\", \"AesOfb\" and "
"\"AesScte\".");
ABSL_FLAG(
int32_t, ecm_version, 2,
"Version of the generated ECM. Crrently supported versions are 2 and 3.");
ABSL_FLAG(
std::string, ecc_private_signing_key, "",
"Private signing key used to sign ECM data. Must be an elliptic-curve "
"cryptography key.");
#define LISTEN_QUEUE_SIZE (20)
#define BUFFER_SIZE (1024)
@@ -71,6 +78,13 @@ void BuildEcmgConfig(EcmgConfig* config) {
CHECK(StringToCryptoMode(absl::GetFlag(FLAGS_crypto_mode),
&config->crypto_mode))
<< "Unknown crypto mode.";
CHECK(absl::GetFlag(FLAGS_ecm_version) <= 3)
<< "Invalid ecm version. Must be 2 or 3.";
config->ecm_version = absl::GetFlag(FLAGS_ecm_version) <= 2
? widevine::cas::EcmVersion::kV2
: widevine::cas::EcmVersion::kV3;
config->ecc_private_signing_key =
absl::GetFlag(FLAGS_ecc_private_signing_key);
}
void PrintMessage(const std::string& description, const char* const message,