Export media_cas_packager_sdk
This commit is contained in:
50
media_cas_packager_sdk/internal/BUILD
Normal file
50
media_cas_packager_sdk/internal/BUILD
Normal file
@@ -0,0 +1,50 @@
|
||||
################################################################################
|
||||
# Copyright 2018 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.
|
||||
################################################################################
|
||||
|
||||
# Build file for ECM SDK internal library.
|
||||
|
||||
# Only accessible by public media_cas_packager_sdk apis.
|
||||
package_group(
|
||||
name = "internal",
|
||||
packages = [
|
||||
"//media_cas_packager_sdk/...",
|
||||
],
|
||||
)
|
||||
|
||||
package(
|
||||
default_visibility = [":internal"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm",
|
||||
srcs = ["ecm.cc"],
|
||||
hdrs = ["ecm.h"],
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//util:status",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:random_util",
|
||||
"//common:string_util",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
"//protos/public:media_cas_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_test",
|
||||
size = "small",
|
||||
srcs = ["ecm_test.cc"],
|
||||
deps = [
|
||||
":ecm",
|
||||
"//testing:gunit_main",
|
||||
"//util:status",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
543
media_cas_packager_sdk/internal/ecm.cc
Normal file
543
media_cas_packager_sdk/internal/ecm.cc
Normal file
@@ -0,0 +1,543 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "util/status.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "common/random_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
// ECM constants
|
||||
static constexpr size_t kMaxEcmSizeBytes = 184;
|
||||
|
||||
// Bitfield lengths for the mp2ts ecm payload.
|
||||
// This ECM structure is specific to the Widevine CAS implementation.
|
||||
|
||||
// Byte 0, 1
|
||||
static constexpr int kNumBitsCaSystemIdField = 16;
|
||||
|
||||
// Byte 2
|
||||
static constexpr int kNumBitsEcmVersionField = 8;
|
||||
|
||||
// Byte 3
|
||||
static constexpr int kNumBitsEcmGenerationCountField = 5;
|
||||
// Values for Decrypt Mode are from enum CasCryptoMode
|
||||
static constexpr int kNumBitsDecryptModeField = 2;
|
||||
static constexpr int kNumBitsRotationEnabledField = 1;
|
||||
|
||||
static constexpr int kMaxGeneration =
|
||||
(1 << kNumBitsEcmGenerationCountField) - 1;
|
||||
|
||||
// Byte 4
|
||||
// Size of IVs.
|
||||
// Values for IV size fields are from enum EcmIvSize
|
||||
// Note: Wrapped key IV size is always 16. The field is encoded, but it must
|
||||
// always be set to 1.
|
||||
static constexpr int kNumBitsWrappedKeyIvSizeField = 1;
|
||||
static constexpr int kNumBitsContentIvSizeField = 1;
|
||||
// Unused bits (mbz, must be zero)
|
||||
static constexpr int kNumBitsUnusedField = 6;
|
||||
|
||||
// Remaining bytes (starting from the 6th byte) hold entitled key info.
|
||||
static constexpr size_t kKeyIdSizeBytes = 16;
|
||||
static constexpr size_t kKeyDataSizeBytes = 16;
|
||||
static constexpr size_t kWrappedKeyIvSizeBytes = 16;
|
||||
|
||||
// BitField constants for the ECM payload
|
||||
|
||||
// CA System ID for Widevine. From table in
|
||||
// https://en.wikipedia.org/wiki/Conditional_access
|
||||
// This should be the only file found.
|
||||
static constexpr int kWvCasCaSystemId = 0x4AD4;
|
||||
|
||||
// Version - this should be incremented if there are non-backwards compatible
|
||||
// changes to the ECM.
|
||||
static constexpr int kEcmVersion = 1;
|
||||
|
||||
// Settings for RotationEnabled field.
|
||||
static constexpr int kRotationDisabled = 0;
|
||||
static constexpr int kRotationEnabled = 1;
|
||||
|
||||
// Convert from boolean for rotation to rotation field value.
|
||||
int RotationFieldValue(bool rotation_required) {
|
||||
return rotation_required ? kRotationEnabled : kRotationDisabled;
|
||||
}
|
||||
|
||||
// Setting for Unused field.
|
||||
static constexpr int kUnusedZero = 0;
|
||||
|
||||
// Convert from IV size to IV size field value.
|
||||
int IvSizeFieldValue(size_t iv_size) {
|
||||
return (8 == iv_size) ? kIvSize8 : kIvSize16;
|
||||
}
|
||||
|
||||
void SerializeKeyInfo(const EntitledKeyInfo& key_info, std::string* ecm_buf) {
|
||||
absl::StrAppend(ecm_buf, key_info.entitlement_key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.key_id);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_value);
|
||||
absl::StrAppend(ecm_buf, key_info.wrapped_key_iv);
|
||||
absl::StrAppend(ecm_buf, key_info.content_iv);
|
||||
}
|
||||
|
||||
bool ConvertIvSizeParam(EcmIvSize param, size_t* size) {
|
||||
if (param == kIvSize8) {
|
||||
*size = 8;
|
||||
} else if (param == kIvSize16) {
|
||||
*size = 16;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
util::Status CasEcm::Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message) {
|
||||
if (initialized_) {
|
||||
return {util::error::INTERNAL, "Already initialized."};
|
||||
}
|
||||
if (content_id.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content ID is empty."};
|
||||
}
|
||||
if (content_provider.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content Provider is empty."};
|
||||
}
|
||||
if (key_request_message == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "key_request_message is null."};
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.track_types.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Parameter track_ids must be set with one or more Track IDs."};
|
||||
}
|
||||
|
||||
if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size,
|
||||
&content_iv_size_)) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
|
||||
}
|
||||
|
||||
if (ecm_init_parameters.crypto_mode != CasCryptoMode::CTR &&
|
||||
ecm_init_parameters.crypto_mode != CasCryptoMode::CBC) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Crypto mode setting is out of range."};
|
||||
} else {
|
||||
crypto_mode_ = ecm_init_parameters.crypto_mode;
|
||||
}
|
||||
|
||||
content_id_ = content_id;
|
||||
content_provider_ = content_provider;
|
||||
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
|
||||
track_types_ = ecm_init_parameters.track_types;
|
||||
// Generation is incremented before the ECM is generated.
|
||||
// Initializing to kMaxGeneration ensures the first generated ECM has a gen
|
||||
// count of zero.
|
||||
generation_ = kMaxGeneration;
|
||||
|
||||
// Construct and return CasEncryptionRequest message for caller to use.
|
||||
util::Status status = CreateEntitlementRequest(key_request_message);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Entitlement request message could not be created.";
|
||||
return status;
|
||||
}
|
||||
|
||||
// Everything is set up except entitlement keys.
|
||||
ClearEntitlementKeys();
|
||||
initialized_ = true;
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ProcessCasEncryptionResponse(const std::string& response) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (response.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Response std::string is empty."};
|
||||
}
|
||||
return ParseEntitlementResponse(response);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {util::error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (!paired_keys_required_) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Key rotation not enabled - use GenerateSingleKeyEcm()."};
|
||||
}
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(even_key);
|
||||
keys.push_back(odd_key);
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (!HaveEntitlementKeys()) {
|
||||
return {util::error::INTERNAL, "Need entitlement key."};
|
||||
}
|
||||
if (paired_keys_required_) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Key rotation enabled - use GenerateEcm()."};
|
||||
}
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(key);
|
||||
|
||||
return GenerateEcmCommon(keys, track_type, serialized_ecm, generation);
|
||||
}
|
||||
|
||||
util::Status CasEcm::GenerateEcmCommon(
|
||||
const std::vector<EntitledKeyInfo*>& keys, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation) {
|
||||
if (serialized_ecm == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "No return ecm std::string pointer."};
|
||||
}
|
||||
if (generation == nullptr) {
|
||||
return {util::error::INVALID_ARGUMENT, "No return generation pointer."};
|
||||
}
|
||||
|
||||
util::Status status = ValidateKeys(keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = WrapEntitledKeys(track_type, keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = ValidateWrappedKeys(keys);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// TODO(user): validate inputs, compare against current values
|
||||
// TODO(user): replace current values.
|
||||
|
||||
// Updates complete, we are complete and consistent.
|
||||
// Update generation before serializing.
|
||||
uint32_t previous_generation = generation_;
|
||||
IncrementGeneration();
|
||||
|
||||
// Generate TS packet payload for ECM, pass back to caller.
|
||||
serialized_ecm->assign(SerializeEcm(keys));
|
||||
if (kMaxEcmSizeBytes < serialized_ecm->size()) {
|
||||
generation_ = previous_generation;
|
||||
serialized_ecm->clear();
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"Maximum size of ECM has been exceeded.");
|
||||
}
|
||||
*generation = generation_;
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
void CasEcm::IncrementGeneration() {
|
||||
generation_ = (generation_ >= kMaxGeneration) ? 0 : generation_ + 1;
|
||||
}
|
||||
|
||||
util::Status CasEcm::WrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*> keys) {
|
||||
if (!initialized_) {
|
||||
return {util::error::INTERNAL, "Not initialized."};
|
||||
}
|
||||
if (keys.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Vector of EntitledKeyInfo is empty."};
|
||||
}
|
||||
|
||||
auto ekey_map_entry = entitlement_keys_.find(track_type);
|
||||
if (ekey_map_entry == entitlement_keys_.end()) {
|
||||
return {util::error::INTERNAL,
|
||||
"No Entitlement Key found for given track_type."};
|
||||
}
|
||||
|
||||
const auto& ekey_list = ekey_map_entry->second;
|
||||
if (ekey_list.size() != keys.size()) {
|
||||
return {util::error::INTERNAL,
|
||||
"Number of Entitled keys and Entitlement keys must match."};
|
||||
}
|
||||
|
||||
auto entitlement_key = ekey_list.begin();
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = entitlement_key->key_id;
|
||||
// Wrap key using entitlement key. First generate new IV.
|
||||
CHECK(RandomBytes(kWrappedKeyIvSizeBytes, &entitled_key->wrapped_key_iv));
|
||||
entitled_key->wrapped_key_value =
|
||||
WrapKey(entitlement_key->key_value, entitled_key->wrapped_key_iv,
|
||||
entitled_key->key_value);
|
||||
entitlement_key++;
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
std::string CasEcm::WrapKey(const std::string& wrapping_key, const std::string& iv,
|
||||
const std::string& key_value) {
|
||||
// Wrapped key IV is always 16 bytes.
|
||||
return crypto_util::EncryptAesCbcNoPad(wrapping_key, iv, key_value);
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeys(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (const auto& key : keys) {
|
||||
util::Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->content_iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (const auto& key : keys) {
|
||||
util::Status status;
|
||||
status = ValidateKeyId(key->key_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateKeyValue(key->wrapped_key_value);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Wrapped key is bad.";
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->wrapped_key_iv, kWrappedKeyIvSizeBytes);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
status = ValidateIv(key->content_iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeyId(const std::string& key_id) {
|
||||
if (key_id.size() != kKeyIdSizeBytes) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key ID must be 16 bytes."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateKeyValue(const std::string& key_value) {
|
||||
if (key_value.size() != kKeyDataSizeBytes) {
|
||||
util::Status(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
absl::StrCat("Key is wrong size (", key_value.size(), " bytes)."));
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ValidateIv(const std::string& iv, size_t size) {
|
||||
if (iv.size() != size) {
|
||||
return {util::error::INVALID_ARGUMENT, "IV is wrong size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
// Five bytes (40 bits including padding)
|
||||
std::bitset<kNumBitsCaSystemIdField> ca_system_id(kWvCasCaSystemId);
|
||||
std::bitset<kNumBitsEcmVersionField> ecm_version(kEcmVersion);
|
||||
std::bitset<kNumBitsEcmGenerationCountField> ecm_generation_count(
|
||||
generation());
|
||||
std::bitset<kNumBitsDecryptModeField> decrypt_mode(crypto_mode());
|
||||
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
|
||||
RotationFieldValue(paired_keys_required()));
|
||||
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(
|
||||
IvSizeFieldValue(kWrappedKeyIvSizeBytes));
|
||||
std::bitset<kNumBitsContentIvSizeField> content_iv_size(
|
||||
IvSizeFieldValue(this->content_iv_size()));
|
||||
std::bitset<kNumBitsUnusedField> padding(kUnusedZero);
|
||||
|
||||
// Converts bitset to string.
|
||||
std::string ecm_bitset = absl::StrCat(
|
||||
ca_system_id.to_string(), ecm_version.to_string(),
|
||||
ecm_generation_count.to_string(), decrypt_mode.to_string(),
|
||||
rotation_enabled.to_string(), wrapped_key_iv_size.to_string(),
|
||||
content_iv_size.to_string(), padding.to_string());
|
||||
if (ecm_bitset.size() != 40) {
|
||||
LOG(FATAL) << "ECM bitset incorret size: " << ecm_bitset.size();
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
util::Status status =
|
||||
string_util::BitsetStringToBinaryString(ecm_bitset, &serialized_ecm);
|
||||
if (!status.ok()) {
|
||||
LOG(FATAL) << "Failed to convert ECM bitset to std::string";
|
||||
}
|
||||
|
||||
// Appends entitled key info.
|
||||
for (const auto& key : keys) {
|
||||
SerializeKeyInfo(*key, &serialized_ecm);
|
||||
}
|
||||
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
util::Status CasEcm::CreateEntitlementRequest(std::string* request_string) {
|
||||
CasEncryptionRequest request;
|
||||
|
||||
request.set_content_id(content_id_);
|
||||
request.set_provider(content_provider_);
|
||||
request.set_key_rotation(paired_keys_required_);
|
||||
// Add labels for tracks.
|
||||
for (const auto& track_type : track_types_) {
|
||||
request.add_track_types(track_type);
|
||||
}
|
||||
|
||||
if (!request.SerializeToString(request_string)) {
|
||||
request_string->clear();
|
||||
return {util::error::INTERNAL, "Failure serializing request."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcm::ParseEntitlementResponse(const std::string& response_string) {
|
||||
// TODO(user): parse valid response. NOT Implemented.
|
||||
ClearEntitlementKeys();
|
||||
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
if (!signed_response.ParseFromString(response_string)) {
|
||||
return {util::error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
// TODO(user): Should verify signature.
|
||||
CasEncryptionResponse response;
|
||||
if (!response.ParseFromString(signed_response.response())) {
|
||||
return {util::error::INTERNAL, "Failure parsing signed response."};
|
||||
}
|
||||
if (response.status() != CasEncryptionResponse_Status_OK) {
|
||||
return util::Status(
|
||||
util::error::INTERNAL,
|
||||
absl::StrCat("Failure reported by server: ", response.status(), " : ",
|
||||
response.status_message()));
|
||||
}
|
||||
if (content_id_ != response.content_id()) {
|
||||
return util::Status(
|
||||
util::error::INTERNAL,
|
||||
absl::StrCat("Content ID mismatch in Entitlement Response - expected: ",
|
||||
content_id_, " received: ", response.content_id()));
|
||||
}
|
||||
if (response.entitlement_keys().empty()) {
|
||||
return {util::error::INTERNAL, "Failure: no entitlement keys in response."};
|
||||
}
|
||||
size_t keys_needed = (paired_keys_required_ ? 2 : 1) * track_types_.size();
|
||||
if (keys_needed > response.entitlement_keys().size()) {
|
||||
return util::Status(
|
||||
util::error::INTERNAL,
|
||||
absl::StrCat(
|
||||
"Wrong number of keys in Entitlement Response - expected: ",
|
||||
keys_needed, " got: ", response.entitlement_keys().size()));
|
||||
}
|
||||
|
||||
// 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.
|
||||
ClearEntitlementKeys();
|
||||
for (const auto& key : response.entitlement_keys()) {
|
||||
if (!key.has_track_type()) {
|
||||
LOG(WARNING) << "Entitlement key missing track type, skipped.";
|
||||
// No track ID. Skip it.
|
||||
continue;
|
||||
}
|
||||
EntitlementKeyInfo ekey;
|
||||
ekey.key_id = key.key_id();
|
||||
ekey.key_value = key.key();
|
||||
|
||||
// Using only keys with correct KeySlot
|
||||
if (!key.has_key_slot()) {
|
||||
LOG(WARNING) << "Entitlement key missing key slot, skipped.";
|
||||
continue;
|
||||
}
|
||||
if (paired_keys_required()) {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_EVEN) {
|
||||
PushEntitlementKey(key.track_type(), true, ekey);
|
||||
} else if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_ODD) {
|
||||
PushEntitlementKey(key.track_type(), false, ekey);
|
||||
}
|
||||
} else {
|
||||
if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_SINGLE) {
|
||||
PushEntitlementKey(key.track_type(), false, ekey);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!CheckEntitlementKeys()) {
|
||||
LOG(ERROR) << "Could not stage entitlement keys from response:";
|
||||
response.ShortDebugString();
|
||||
return util::Status(util::error::INTERNAL,
|
||||
"No suitable entitlement key was found.");
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
size_t CasEcm::CountEntitlementKeys() const {
|
||||
size_t count = 0;
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
count += track.second.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool CasEcm::CheckEntitlementKeys() const {
|
||||
// TODO(user): Cross-check entitlement_keys_ track types with track_types_.
|
||||
if (track_types_.size() > CountEntitlementTracks()) {
|
||||
// One or more tracks are missing.
|
||||
LOG(ERROR)
|
||||
<< "Entitlement keys for one or more tracks is missing - expected "
|
||||
<< track_types_.size() << " tracks, received "
|
||||
<< CountEntitlementTracks() << "tracks.";
|
||||
return false;
|
||||
}
|
||||
for (const auto& track : entitlement_keys_) {
|
||||
if (track.second.size() < (paired_keys_required() ? 2 : 1)) {
|
||||
LOG(ERROR) << " Wrong number of entitlement keys for track "
|
||||
<< track.first << ": " << (paired_keys_required() ? 2 : 1)
|
||||
<< " expected, " << track.second.size() << " received.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
244
media_cas_packager_sdk/internal/ecm.h
Normal file
244
media_cas_packager_sdk/internal/ecm.h
Normal file
@@ -0,0 +1,244 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "util/status.h"
|
||||
#include "protos/public/media_cas.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// Information needed to generate entitled key portion of ECM. This will be
|
||||
// set up by the caller and passed into WrapEntitledKey() and GenerateEcm().
|
||||
struct EntitledKeyInfo {
|
||||
// |key_id|, |key_value|, and |content_iv| are set up by the API client
|
||||
// before calling GenerateEcm() or GenerateSingleKeyEcm().
|
||||
std::string key_id;
|
||||
std::string key_value;
|
||||
std::string content_iv;
|
||||
// A key that has been wrapped has the
|
||||
// following fields set. This is done by WrapEntitledKeys().
|
||||
std::string entitlement_key_id;
|
||||
std::string wrapped_key_value; // always encrypted using AES CBC
|
||||
std::string wrapped_key_iv;
|
||||
};
|
||||
|
||||
// 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 to start a new ECM stream.
|
||||
// Fields:
|
||||
// |content_iv_size| size of all content key IVs in the ECM stream.
|
||||
// A constant of type CasEcmSize 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 CasCryptoMode.
|
||||
// |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.
|
||||
struct EcmInitParameters {
|
||||
EcmIvSize content_iv_size = kIvSize8;
|
||||
bool key_rotation_enabled = true;
|
||||
CasCryptoMode crypto_mode = CasCryptoMode::CTR;
|
||||
std::vector<std::string> track_types;
|
||||
};
|
||||
|
||||
// Generator for producing Widevine CAS-compliant ECMs. Used to construct the
|
||||
// Transport Stream packet payload of an ECM containing key information for
|
||||
// decrypting Widevine CAS encrypted content. The keys in the ECM are wrapped
|
||||
// (encrypted) using the entitlement key associated with the content.
|
||||
// The entitlement key ID is included in the ECM, and it is used to get and
|
||||
// stage the entitlement key needed to unwrap the content keys.
|
||||
// ECMs may hold one or two keys. If key rotation is enabled, the ECM holds a
|
||||
// pair of keys (even and odd) to facilitate key rotation.
|
||||
//
|
||||
// TODO(user): Add usage example.
|
||||
//
|
||||
// Class CasEcm is not thread safe.
|
||||
class CasEcm {
|
||||
public:
|
||||
CasEcm() = default;
|
||||
virtual ~CasEcm() = default;
|
||||
|
||||
// Perform initialization for a new ECM stream.
|
||||
// Args:
|
||||
// |content_id| uniquely identifies the content (with |content_provider|)
|
||||
// |content_provider| unique std::string for provider of the content stream.
|
||||
// |wv_ECM_parameters| encryption-related parameters for configuring
|
||||
// the ECM stream.
|
||||
// |key_request_message| pointer to a std::string to receive a CasEncryptionRequest
|
||||
// message.
|
||||
// Notes:
|
||||
// The returned |key_request_message| must be sent to the server and
|
||||
// the response correctly parsed before ECMs can be generated.
|
||||
virtual util::Status Initialize(const std::string& content_id,
|
||||
const std::string& content_provider,
|
||||
const EcmInitParameters& ecm_init_parameters,
|
||||
std::string* key_request_message);
|
||||
|
||||
// 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| a serialized CasEncryptionRequest message from the server
|
||||
// holding entitlement key information (or error information).
|
||||
virtual util::Status ProcessCasEncryptionResponse(const std::string& response);
|
||||
|
||||
// Accept keys and IVs and construct an ECM that will fit into a Transport
|
||||
// Stream packet payload (184 bytes).
|
||||
// Args:
|
||||
// |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.
|
||||
// |generation| pointer to uint32_t to receive the generation number of 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.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual util::Status GenerateEcm(EntitledKeyInfo* even_key,
|
||||
EntitledKeyInfo* odd_key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
|
||||
// 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.
|
||||
// |generation| pointer to uint32_t to receive the generation number of the ECM.
|
||||
// The |key| contents (specifically IV sizes) must be consistent
|
||||
// with the initialized settings.
|
||||
// Generation is a mod 32 counter. If the ECM has any changes from the
|
||||
// previous ECM, the generation is increased by one.
|
||||
virtual util::Status GenerateSingleKeyEcm(EntitledKeyInfo* key,
|
||||
const std::string& track_type,
|
||||
std::string* serialized_ecm,
|
||||
uint32_t* generation);
|
||||
|
||||
protected: // For unit tests.
|
||||
// Take the input entitled |keys| and our current state, and generate
|
||||
// the ECM representing the keys.
|
||||
virtual std::string SerializeEcm(const std::vector<EntitledKeyInfo*>& keys);
|
||||
|
||||
// Increment the ECM's generation count by one (mod kMaxGeneration).
|
||||
virtual void IncrementGeneration();
|
||||
|
||||
// Apply the Entitlement key with the given track type and polarity
|
||||
// (even vs. odd key) to wrap the given Entitled key.
|
||||
// Args:
|
||||
// |track_type| the track type for the keys. The type_track must match one
|
||||
// of the |EcmInitParameters::track_types| strings passed into Initialize().
|
||||
// |key| the Entitled Key to be wrapped.
|
||||
virtual util::Status WrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*> keys);
|
||||
|
||||
private:
|
||||
// Entitlement key - |key_value| is used to wrap the content key, and |key_id|
|
||||
// is added to the ECM. |track_type| allows the entitlement key to be
|
||||
// associated with the original request. See Initialize() for details.
|
||||
struct EntitlementKeyInfo {
|
||||
std::string key_id;
|
||||
std::string key_value;
|
||||
};
|
||||
|
||||
// Check whether we've acquired entitlement keys.
|
||||
bool HaveEntitlementKeys() const { return !entitlement_keys_.empty(); }
|
||||
|
||||
// Count the different tracks for which we have entitlement keys.
|
||||
size_t CountEntitlementTracks() const { return entitlement_keys_.size(); }
|
||||
|
||||
// Count the total number of entitlement keys in our current state.
|
||||
size_t CountEntitlementKeys() const;
|
||||
|
||||
// Verify that each track has the right number of keys. Complain if not.
|
||||
bool CheckEntitlementKeys() const;
|
||||
|
||||
// Remove all current entitlement keys.
|
||||
void ClearEntitlementKeys() { entitlement_keys_.clear(); }
|
||||
|
||||
// Add an entitlement key to our current state. Even key is placed first.
|
||||
void PushEntitlementKey(const std::string& track_type, bool is_even_key,
|
||||
const EntitlementKeyInfo& key) {
|
||||
auto emplaced =
|
||||
entitlement_keys_.emplace(track_type, std::list<EntitlementKeyInfo>{});
|
||||
if (is_even_key) {
|
||||
emplaced.first->second.push_front(key);
|
||||
} else {
|
||||
emplaced.first->second.push_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Common helper for GenerateEcm() and GenerateSingleKeyEcm()
|
||||
virtual util::Status GenerateEcmCommon(
|
||||
const std::vector<EntitledKeyInfo*>& keys, const std::string& track_type,
|
||||
std::string* serialized_ecm, uint32_t* generation);
|
||||
|
||||
// Wrap a |key_value| using |wrapping_key| (entitlement key) and |iv|.
|
||||
// Returns the resulting wrapped key.
|
||||
virtual std::string WrapKey(const std::string& wrapping_key, const std::string& iv,
|
||||
const std::string& key_value);
|
||||
|
||||
virtual util::Status ValidateKeys(const std::vector<EntitledKeyInfo*>& keys);
|
||||
virtual util::Status ValidateWrappedKeys(
|
||||
const std::vector<EntitledKeyInfo*>& keys);
|
||||
|
||||
util::Status ValidateKeyId(const std::string& key_id);
|
||||
util::Status ValidateKeyValue(const std::string& key_value);
|
||||
util::Status ValidateIv(const std::string& iv, size_t size);
|
||||
|
||||
// TODO(user): need unit tests for CreateEntitlementRequest.
|
||||
virtual util::Status CreateEntitlementRequest(std::string* request_string);
|
||||
// TODO(user): need unit tests for ParseEntitlementResponse.
|
||||
virtual util::Status ParseEntitlementResponse(const std::string& response_string);
|
||||
|
||||
virtual uint32_t generation() const { return generation_; }
|
||||
virtual CasCryptoMode crypto_mode() const { return crypto_mode_; }
|
||||
virtual bool paired_keys_required() const { return paired_keys_required_; }
|
||||
virtual size_t content_iv_size() const { return content_iv_size_; }
|
||||
|
||||
// Set to true when the object has been properly initialized.
|
||||
bool initialized_ = false;
|
||||
// Current generation. This will be incremented (mod 32) each time a new
|
||||
// ECM has changes from the previous ECM.
|
||||
uint32_t generation_ = 0;
|
||||
// Content ID for this ECM stream.
|
||||
std::string content_id_;
|
||||
// Provider ID for this ECM stream.
|
||||
std::string content_provider_;
|
||||
// Content IV size may be 8 or 16. Size is set once during Initialize().
|
||||
size_t content_iv_size_ = 8;
|
||||
// The set of tracks that are being encrypted and require ECM streams.
|
||||
std::vector<std::string> track_types_;
|
||||
// Remember if a pair of keys is required (for key rotation).
|
||||
bool paired_keys_required_ = false;
|
||||
CasCryptoMode crypto_mode_ = CasCryptoMode::CTR;
|
||||
// Entitlement keys needed for ECM generation.
|
||||
// The keys are added when the CasEncryptionResponse is processed.
|
||||
std::map<std::string, std::list<EntitlementKeyInfo>> entitlement_keys_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_H_
|
||||
646
media_cas_packager_sdk/internal/ecm_test.cc
Normal file
646
media_cas_packager_sdk/internal/ecm_test.cc
Normal file
@@ -0,0 +1,646 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 Google LLC.
|
||||
//
|
||||
// This software is licensed under the terms defined in the Widevine Master
|
||||
// License Agreement. For a copy of this agreement, please contact
|
||||
// widevine-licensing@google.com.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "util/status.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
using ::testing::Return;
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// TODO(user): Encryption options yield a small number of possible sizes
|
||||
// for ECMS - Explain the distinct cases and add/use literals for each one.
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kTrackTypeSD[] = "SD";
|
||||
constexpr char kTrackTypeHD[] = "HD";
|
||||
constexpr char kTrackTypeAUDIO[] = "AUDIO";
|
||||
constexpr char kTrackTypeBAD[] = "BAD";
|
||||
|
||||
constexpr size_t kEcmHeaderSize = 5;
|
||||
constexpr size_t kEcmKeyIdSize = 16;
|
||||
constexpr size_t kEcmKeyDataSize = 16;
|
||||
constexpr size_t kEcmIvSize16 = 16;
|
||||
constexpr size_t kEcmIvSize8 = 8;
|
||||
|
||||
constexpr size_t kEcmKeyInfoSize =
|
||||
kEcmKeyIdSize + kEcmKeyIdSize + kEcmKeyDataSize;
|
||||
|
||||
size_t IvExpectedSize(EcmIvSize iv_size) {
|
||||
return (iv_size == kIvSize8) ? kEcmIvSize8 : kEcmIvSize16;
|
||||
}
|
||||
|
||||
void InitEntitledKey(EntitledKeyInfo* key, const std::string& id,
|
||||
const std::string& value, const std::string& iv) {
|
||||
key->key_id = id;
|
||||
key->key_value = value;
|
||||
key->content_iv = iv;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MockCasEcm : public CasEcm {
|
||||
public:
|
||||
MockCasEcm() = default;
|
||||
~MockCasEcm() override = default;
|
||||
|
||||
MOCK_CONST_METHOD0(generation, uint32_t());
|
||||
MOCK_CONST_METHOD0(crypto_mode, CasCryptoMode());
|
||||
MOCK_CONST_METHOD0(paired_keys_required, bool());
|
||||
MOCK_CONST_METHOD0(content_iv_size, size_t());
|
||||
|
||||
std::string CallSerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
|
||||
return SerializeEcm(keys);
|
||||
}
|
||||
|
||||
virtual util::Status MockWrapEntitledKeys(
|
||||
const std::string& track_type, const std::vector<EntitledKeyInfo*>& keys) {
|
||||
for (auto entitled_key : keys) {
|
||||
entitled_key->entitlement_key_id = "entitlement_Mock";
|
||||
entitled_key->wrapped_key_value = "MockMockMockMock";
|
||||
entitled_key->wrapped_key_iv = "WRAPPED_KIV....x";
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
void MockSetup(bool two_keys, const std::string& track_type, uint32_t generation,
|
||||
CasCryptoMode crypto_mode, size_t civ_size) {
|
||||
EXPECT_CALL(*this, generation()).WillRepeatedly(Return(generation));
|
||||
EXPECT_CALL(*this, crypto_mode()).WillRepeatedly(Return(crypto_mode));
|
||||
EXPECT_CALL(*this, paired_keys_required()).WillRepeatedly(Return(two_keys));
|
||||
EXPECT_CALL(*this, content_iv_size()).WillRepeatedly(Return(civ_size));
|
||||
}
|
||||
};
|
||||
|
||||
class CasEcmTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
InitEntitledKey(&valid1_iv_16_8_, "keyid_16b......1", "keyvalue_16b...1",
|
||||
"ct_iv..1");
|
||||
InitEntitledKey(&valid2_iv_16_8_, "wkv_16b.2.4.6.81", "wiv_16b.a.b.c.d1",
|
||||
"ct_iv..2");
|
||||
InitEntitledKey(&valid3_iv_16_16_, "keyid_16b......3", "keyvalue_16b...3",
|
||||
"content_iv.....3");
|
||||
InitEntitledKey(&valid4_iv_16_16_, "keyid_16b....xx4", "keyvalue_16b...4",
|
||||
"content_iv.....4");
|
||||
InitEntitledKey(&invalid1_key_id_iv_16_8_, "keyid_12b..1",
|
||||
"keyvalue_16b...1", "ct_iv..5");
|
||||
|
||||
params_one_key_.key_rotation_enabled = false;
|
||||
params_two_keys_.key_rotation_enabled = true;
|
||||
params_simple_.track_types.push_back(kTrackTypeSD);
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params, const std::string& track_type) {
|
||||
params->track_types.push_back(track_type);
|
||||
}
|
||||
|
||||
virtual void InitParams(EcmInitParameters* params, const std::string& track_type,
|
||||
EcmIvSize content_iv) {
|
||||
params->track_types.push_back(track_type);
|
||||
params->content_iv_size = content_iv;
|
||||
}
|
||||
|
||||
virtual void ServerCall(const std::string& request_string,
|
||||
std::string* signed_response_string, bool report_status_ok,
|
||||
bool generate_valid_ecm) {
|
||||
CasEncryptionRequest request;
|
||||
ASSERT_TRUE(request.ParseFromString(request_string));
|
||||
|
||||
CasEncryptionResponse response;
|
||||
if (!report_status_ok) {
|
||||
response.set_status(CasEncryptionResponse_Status_INTERNAL_ERROR);
|
||||
} else {
|
||||
response.set_status(CasEncryptionResponse_Status_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_KeySlot_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_KeySlot_ODD);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
} 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_KeySlot_SINGLE);
|
||||
if (!generate_valid_ecm) {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id("fake_key_id.....");
|
||||
key->set_key("fakefakefakefakefakefakefakefake");
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
key->set_track_type(track_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
ASSERT_TRUE(response.SerializeToString(&response_string));
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
ASSERT_TRUE(signed_response.SerializeToString(signed_response_string));
|
||||
}
|
||||
|
||||
const std::string provider_ = "provider";
|
||||
const std::string content_id_ = "content-id";
|
||||
EcmInitParameters params_one_key_;
|
||||
EcmInitParameters params_two_keys_;
|
||||
EcmInitParameters params_simple_;
|
||||
EcmInitParameters params_default_;
|
||||
EntitledKeyInfo valid1_iv_16_8_;
|
||||
EntitledKeyInfo valid2_iv_16_8_;
|
||||
EntitledKeyInfo valid3_iv_16_16_;
|
||||
EntitledKeyInfo valid4_iv_16_16_;
|
||||
EntitledKeyInfo invalid1_key_id_iv_16_8_;
|
||||
};
|
||||
|
||||
class CasEcmSerializeEcmTest : public CasEcmTest {
|
||||
public:
|
||||
void ValidateEcmHeaderFields(const std::string& ecm_string, bool rotation_enabled,
|
||||
int gen, CasCryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
EXPECT_THAT('\x4A', ecm_string[0]);
|
||||
EXPECT_THAT('\xD4', ecm_string[1]);
|
||||
EXPECT_THAT('\x01', ecm_string[2]); // version
|
||||
EXPECT_THAT(gen, ((ecm_string[3] >> 3) & 31));
|
||||
EXPECT_THAT(static_cast<int>(crypto_mode), ((ecm_string[3] >> 1) & 3));
|
||||
EXPECT_THAT(rotation_enabled ? 1 : 0, ecm_string[3] & 1);
|
||||
EXPECT_THAT(1, ((ecm_string[4] >> 7) & 1)); // wrapped key IV size, MB 1
|
||||
EXPECT_THAT(static_cast<int>(content_iv), ((ecm_string[4] >> 6) & 1));
|
||||
EXPECT_THAT(0, ecm_string[4] & 63); // zero padding
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsOneKey(const std::string& buf_string, int gen,
|
||||
CasCryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size = kEcmHeaderSize + kEcmKeyInfoSize + kEcmIvSize16 +
|
||||
IvExpectedSize(content_iv);
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, false, gen, crypto_mode, content_iv);
|
||||
}
|
||||
|
||||
void ValidateEcmFieldsTwoKeys(const std::string& buf_string, int gen,
|
||||
CasCryptoMode crypto_mode,
|
||||
EcmIvSize content_iv) {
|
||||
size_t expected_size =
|
||||
kEcmHeaderSize +
|
||||
(2 * (kEcmKeyInfoSize + kEcmIvSize16 + IvExpectedSize(content_iv)));
|
||||
size_t ecm_len = buf_string.size();
|
||||
EXPECT_THAT(expected_size, ecm_len);
|
||||
ValidateEcmHeaderFields(buf_string, true, gen, crypto_mode, content_iv);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CasEcmTest, GenerateEcmNotInitialized) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data, &gen)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateEcm2NotInitialized) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
EntitledKeyInfo key2;
|
||||
std::string ecm_data;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data, &gen)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, SetResponseNotInitialized) {
|
||||
CasEcm ecm_gen;
|
||||
const std::string response("anything");
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, InitNoTracksFail) {
|
||||
CasEcm ecm_gen;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_default_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, InitSucceed) {
|
||||
CasEcm ecm_gen;
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, SecondInitFail) {
|
||||
CasEcm ecm_gen;
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request));
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, InitEmptyContentIdFail) {
|
||||
CasEcm ecm_gen;
|
||||
const std::string empty_content_id;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(empty_content_id, provider_, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, InitEmptyProviderFail) {
|
||||
CasEcm ecm_gen;
|
||||
const std::string empty_provider;
|
||||
std::string request;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, empty_provider, params_simple_, &request)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, InitIvSize16x16OK) {
|
||||
CasEcm ecm_gen;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize16);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateWithNoEntitlementOneKeyFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
// This will fail because the entitlement key has not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateWithNoEntitlementTwoKeysFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1;
|
||||
EntitledKeyInfo key2;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
// This will fail because the entitlement keys have not been acquired
|
||||
// (via server call and ProcessCasEncryptionResponse()).
|
||||
EXPECT_EQ(
|
||||
util::error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, RequestNullFail) {
|
||||
CasEcm ecm_gen;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.Initialize(content_id_, provider_, params_simple_, nullptr)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateOneKeyOK) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, BadResponseFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, false, true);
|
||||
|
||||
EXPECT_EQ(util::error::INTERNAL,
|
||||
ecm_gen.ProcessCasEncryptionResponse(response).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateTwoKeysOK) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateOneKeyNoGenFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, nullptr)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateOneKeyBadKeyIdFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = invalid1_key_id_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateOneKeyWrong) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
EXPECT_THAT(77, ecm.size());
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateTwoKeysIvSizeFail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
// IV size mismatch.
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
util::error::INVALID_ARGUMENT,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateTwoKeysIvSize16x8OK) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize8);
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(1, gen);
|
||||
EXPECT_THAT(149, ecm.size());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateTwoKeysIvSize16x16OK) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, true);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(0, gen);
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen));
|
||||
EXPECT_THAT(1, gen);
|
||||
EXPECT_THAT(165, ecm.size());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmTest, GenerateThreeKeysIvSize16x16Fail) {
|
||||
CasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
InitParams(¶ms_two_keys_, kTrackTypeSD, kIvSize16);
|
||||
|
||||
std::string request;
|
||||
ASSERT_OK(
|
||||
ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request));
|
||||
|
||||
std::string response;
|
||||
ServerCall(request, &response, true, false);
|
||||
|
||||
ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response));
|
||||
|
||||
std::string ecm;
|
||||
uint32_t gen;
|
||||
EXPECT_EQ(
|
||||
util::error::INTERNAL,
|
||||
ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code());
|
||||
}
|
||||
|
||||
// TODO(user): Add more unit tests for error paths around SerializeEcm.
|
||||
TEST_F(CasEcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) {
|
||||
MockCasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
EntitledKeyInfo key2 = valid4_iv_16_16_;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, 0, CasCryptoMode::CTR, 16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CasCryptoMode::CTR, kIvSize16);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 1, CasCryptoMode::CTR, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(CasEcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) {
|
||||
MockCasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid3_iv_16_16_;
|
||||
|
||||
ecm_gen.MockSetup(false, kTrackTypeSD, 0, CasCryptoMode::CTR, 16);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CasCryptoMode::CTR, kIvSize16);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 1, CasCryptoMode::CTR, kIvSize16);
|
||||
}
|
||||
|
||||
TEST_F(CasEcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) {
|
||||
MockCasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
EntitledKeyInfo key2 = valid2_iv_16_8_;
|
||||
|
||||
ecm_gen.MockSetup(true, kTrackTypeSD, 0, CasCryptoMode::CTR, 8);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
keys.push_back(&key2);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 0, CasCryptoMode::CTR, kIvSize8);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsTwoKeys(buf_string, 1, CasCryptoMode::CTR, kIvSize8);
|
||||
}
|
||||
|
||||
TEST_F(CasEcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) {
|
||||
MockCasEcm ecm_gen;
|
||||
EntitledKeyInfo key1 = valid1_iv_16_8_;
|
||||
|
||||
ecm_gen.MockSetup(false, kTrackTypeSD, 0, CasCryptoMode::CTR, 8);
|
||||
|
||||
std::vector<EntitledKeyInfo*> keys;
|
||||
keys.push_back(&key1);
|
||||
ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys));
|
||||
|
||||
std::string buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 0, CasCryptoMode::CTR, kIvSize8);
|
||||
|
||||
EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1));
|
||||
|
||||
buf_string = ecm_gen.CallSerializeEcm(keys);
|
||||
|
||||
ValidateEcmFieldsOneKey(buf_string, 1, CasCryptoMode::CTR, kIvSize8);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
68
media_cas_packager_sdk/public/BUILD
Normal file
68
media_cas_packager_sdk/public/BUILD
Normal file
@@ -0,0 +1,68 @@
|
||||
################################################################################
|
||||
# Copyright 2018 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.
|
||||
################################################################################
|
||||
|
||||
# Build file for ECM SDK.
|
||||
|
||||
package(
|
||||
default_visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# Defines the common copts for cc_library targets. The main option here is
|
||||
# -fvisibility=default, which exports symbols from the public APIs in the shared
|
||||
# library.
|
||||
# Note that the shared library should be built with -fvisibility=hidden.
|
||||
PUBLIC_COPTS = ["-fvisibility=default"]
|
||||
|
||||
filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = glob(["*.h"]),
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "libmedia_cas_packager_sdk.so",
|
||||
linkshared = 1,
|
||||
deps = [":ecm_generator"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "libmedia_cas_packager_sdk",
|
||||
srcs = [":libmedia_cas_packager_sdk.so"],
|
||||
hdrs = glob(["*.h"]),
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//util:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "ecm_generator",
|
||||
srcs = ["ecm_generator.cc"],
|
||||
hdrs = ["ecm_generator.h"],
|
||||
copts = PUBLIC_COPTS,
|
||||
deps = [
|
||||
"//base",
|
||||
"@abseil_repo//absl/base:core_headers",
|
||||
"//util:status",
|
||||
"//media_cas_packager_sdk/internal:ecm",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ecm_generator_test",
|
||||
size = "small",
|
||||
srcs = ["ecm_generator_test.cc"],
|
||||
deps = [
|
||||
":ecm_generator",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/memory",
|
||||
"//common:aes_cbc_util",
|
||||
"//protos/public:media_cas_encryption_proto",
|
||||
],
|
||||
)
|
||||
149
media_cas_packager_sdk/public/ecm_generator.cc
Normal file
149
media_cas_packager_sdk/public/ecm_generator.cc
Normal file
@@ -0,0 +1,149 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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/ecm_generator.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
static constexpr int kKeyDataSize = 16;
|
||||
static constexpr int kKeyIvSize8 = 8;
|
||||
static constexpr int kKeyIvSize16 = 16;
|
||||
static constexpr int kMaxBytesKeyIdField = 16;
|
||||
|
||||
std::string CasEcmGenerator::GenerateEcm(const EcmParameters& params) {
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
util::Status status = ProcessEcmParameters(params, &keys);
|
||||
if (!status.ok() || !initialized_) {
|
||||
LOG(ERROR) << " EcmParameters is not set up properly: " << status;
|
||||
return "";
|
||||
}
|
||||
std::string serialized_ecm;
|
||||
uint32_t generation;
|
||||
// TODO(user): need track_type
|
||||
std::string track_type = "SD";
|
||||
if (params.rotation_enabled) {
|
||||
status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
} else {
|
||||
status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm,
|
||||
&generation);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Call to CasEcm's ECM Generator failed: " << status;
|
||||
return "";
|
||||
}
|
||||
return serialized_ecm;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ProcessEcmParameters(
|
||||
const EcmParameters& ecm_params, std::vector<EntitledKeyInfo>* keys) {
|
||||
initialized_ = false;
|
||||
rotation_enabled_ = ecm_params.rotation_enabled;
|
||||
|
||||
// Validate and add key data
|
||||
keys->clear();
|
||||
uint32_t keys_needed = ecm_params.rotation_enabled ? 2 : 1;
|
||||
if (ecm_params.key_params.size() < keys_needed) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"Number of supplied keys is wrong (check rotation periods)."};
|
||||
}
|
||||
for (int i = 0; i < keys_needed; i++) {
|
||||
keys->emplace_back(EntitledKeyInfo());
|
||||
EntitledKeyInfo& key = keys->back();
|
||||
key.key_id = ecm_params.key_params[i].key_id;
|
||||
key.key_value = ecm_params.key_params[i].key_data;
|
||||
key.content_iv = ecm_params.key_params[i].content_ivs[0];
|
||||
}
|
||||
current_key_index_ = 0;
|
||||
current_key_even_ = true;
|
||||
initialized_ = true;
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyId(const std::string& id) {
|
||||
if (id.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is empty."};
|
||||
}
|
||||
if (id.size() > kMaxBytesKeyIdField) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key id is too long."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyData(const std::string& key_data) {
|
||||
if (key_data.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is empty."};
|
||||
}
|
||||
if (key_data.size() != kKeyDataSize) {
|
||||
return {util::error::INVALID_ARGUMENT, "Key data is wrong size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateIv(const std::string& iv,
|
||||
size_t required_size) {
|
||||
if (iv.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "IV is empty."};
|
||||
}
|
||||
if (required_size != 8 && required_size != 16) {
|
||||
return {util::error::INTERNAL, "IV size has not been set up correctly."};
|
||||
}
|
||||
|
||||
if (iv.size() != required_size) {
|
||||
return {util::error::INVALID_ARGUMENT,
|
||||
"IV has wrong or inconsistent size."};
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateWrappedKeyIv(const std::string& iv) {
|
||||
// All wrapped key IVs must be 16 bytes.
|
||||
util::Status status = ValidateIv(iv, kIvSize16);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Wrapped key IV is not valid: " << status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateContentIv(const std::string& iv) {
|
||||
// If content_iv_size_ is zero, use this IV as the size for all future IVs in
|
||||
// this stream.
|
||||
if (content_iv_size_ == 0) {
|
||||
content_iv_size_ = iv.size();
|
||||
}
|
||||
util::Status status = ValidateIv(iv, content_iv_size_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << " Content IV is not valid: " << status;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
util::Status CasEcmGenerator::ValidateKeyParameters(
|
||||
const KeyParameters& key_params) {
|
||||
util::Status status;
|
||||
status = ValidateKeyId(key_params.key_id);
|
||||
if (!status.ok()) return status;
|
||||
status = ValidateKeyData(key_params.wrapped_key_data);
|
||||
if (!status.ok()) return status;
|
||||
status = ValidateWrappedKeyIv(key_params.wrapped_key_iv);
|
||||
if (!status.ok()) return status;
|
||||
if (key_params.content_ivs.empty()) {
|
||||
return {util::error::INVALID_ARGUMENT, "Content IVs is empty."};
|
||||
}
|
||||
for (int i = 0; i < key_params.content_ivs.size(); i++) {
|
||||
status = ValidateContentIv(key_params.content_ivs[i]);
|
||||
if (!status.ok()) return status;
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
95
media_cas_packager_sdk/public/ecm_generator.h
Normal file
95
media_cas_packager_sdk/public/ecm_generator.h
Normal file
@@ -0,0 +1,95 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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_ECM_GENERATOR_H_
|
||||
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_ECM_GENERATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/status.h"
|
||||
#include "media_cas_packager_sdk/internal/ecm.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
// KeyParameters carries key information for a single encryption key.
|
||||
// Instances of this struct are owned by an EcmParameters struct.
|
||||
struct KeyParameters {
|
||||
std::string key_id;
|
||||
std::string key_data;
|
||||
std::string wrapped_key_data;
|
||||
std::string wrapped_key_iv;
|
||||
std::vector<std::string> content_ivs;
|
||||
};
|
||||
|
||||
// EcmParameters holds information that is needed by the EcmGenerator. It is
|
||||
// partially set up with data from a KeyGenerator (obtained via GenerateKey()).
|
||||
// IVs are added later.
|
||||
// TODO(user): may need a starting crypto period index.
|
||||
struct EcmParameters {
|
||||
static constexpr int kDefaultIVSize = 8;
|
||||
int iv_size = kDefaultIVSize;
|
||||
bool current_key_even = true;
|
||||
uint32_t current_key_index = 0;
|
||||
std::string entitlement_key_id;
|
||||
bool rotation_enabled = true;
|
||||
uint32_t rotation_periods;
|
||||
std::vector<KeyParameters> key_params;
|
||||
uint16_t program_id;
|
||||
uint64_t rotation_period_microseconds;
|
||||
// For video, this is zero. For audio, this is the size of the audio frame,
|
||||
// which is a constant in the audio track.
|
||||
uint16_t offset = 0;
|
||||
};
|
||||
|
||||
// ECM Generator for Widevine/MediaCAS entitled keys.
|
||||
class CasEcmGenerator {
|
||||
public:
|
||||
CasEcmGenerator() = default;
|
||||
virtual ~CasEcmGenerator() = default;
|
||||
|
||||
virtual std::string GenerateEcm(const EcmParameters& params);
|
||||
|
||||
// Query the state of this ECM Generator
|
||||
bool initialized() { return initialized_; }
|
||||
bool rotation_enabled() { return rotation_enabled_; }
|
||||
|
||||
void set_ecm(std::unique_ptr<CasEcm> ecm) { ecm_ = std::move(ecm); }
|
||||
|
||||
private:
|
||||
friend class CasEcmGeneratorTest;
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& ecm_params,
|
||||
std::vector<EntitledKeyInfo>* keys);
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& ecm_params);
|
||||
util::Status ValidateKeyId(const std::string& id);
|
||||
util::Status ValidateKeyData(const std::string& key_data);
|
||||
util::Status ValidateWrappedKeyIv(const std::string& iv);
|
||||
util::Status ValidateIv(const std::string& iv, size_t required_size);
|
||||
util::Status ValidateContentIv(const std::string& iv);
|
||||
util::Status ValidateKeyParameters(const KeyParameters& key_params);
|
||||
|
||||
bool initialized_ = false;
|
||||
uint32_t generation_ = 0;
|
||||
bool rotation_enabled_ = false;
|
||||
uint32_t current_key_index_ = 0;
|
||||
bool current_key_even_ = true;
|
||||
uint32_t content_iv_size_ = 0;
|
||||
std::unique_ptr<CasEcm> ecm_;
|
||||
};
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
|
||||
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_ECM_GENERATOR_H_
|
||||
311
media_cas_packager_sdk/public/ecm_generator_test.cc
Normal file
311
media_cas_packager_sdk/public/ecm_generator_test.cc
Normal file
@@ -0,0 +1,311 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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/ecm_generator.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "common/aes_cbc_util.h"
|
||||
#include "protos/public/media_cas_encryption.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace cas {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kCADescriptor[] = "TestCa";
|
||||
constexpr char kEcm[] = "TestEcm";
|
||||
constexpr char kProvider[] = "Gfiber";
|
||||
constexpr char kContentId[] = "TestContent";
|
||||
constexpr int kDefaultKeyRotationPeriodMilliseconds = 30 * 60 * 1000;
|
||||
constexpr char kPsshData[] = "TestPsshData";
|
||||
|
||||
constexpr char kEntitlementKeySingle[] = "testEKId12345678";
|
||||
constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh";
|
||||
|
||||
constexpr char kEcmKeyIdSingle[] = "key-id-One123456";
|
||||
constexpr char kEcmKeyDataSingle[] = "0123456701234567";
|
||||
constexpr char kEcmWrappedKeySingle[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvSingle[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdEven[] = "key-Id-One123456";
|
||||
constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee";
|
||||
constexpr char kEcmWrappedKeyEven[] = "1234567890123456";
|
||||
constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh";
|
||||
constexpr char kEcmContentIvEven[] = "ABCDEFGH";
|
||||
|
||||
constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789";
|
||||
constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO";
|
||||
constexpr char kEcmWrappedKeyOdd[] = "9876543210654321";
|
||||
constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4";
|
||||
constexpr char kEcmContentIvOdd[] = "AaCbEcGd";
|
||||
|
||||
constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id.....";
|
||||
constexpr char kFakeCasEncryptionResponseKeyData[] =
|
||||
"fakefakefakefakefakefakefakefake";
|
||||
|
||||
util::Status HandleCasEncryptionRequest(const std::string& request_string,
|
||||
std::string* signed_response_string) {
|
||||
CasEncryptionRequest request;
|
||||
request.ParseFromString(request_string);
|
||||
|
||||
CasEncryptionResponse response;
|
||||
response.set_status(CasEncryptionResponse_Status_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(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN);
|
||||
// Add the Odd key.
|
||||
key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD);
|
||||
} else {
|
||||
auto key = response.add_entitlement_keys();
|
||||
key->set_key_id(kFakeCasEncryptionResponseKeyId);
|
||||
key->set_key(kFakeCasEncryptionResponseKeyData);
|
||||
key->set_track_type(track_type);
|
||||
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
|
||||
}
|
||||
}
|
||||
std::string response_string;
|
||||
response.SerializeToString(&response_string);
|
||||
SignedCasEncryptionResponse signed_response;
|
||||
signed_response.set_response(response_string);
|
||||
signed_response.SerializeToString(signed_response_string);
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CasEcmGeneratorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
}
|
||||
|
||||
util::Status ProcessEcmParameters(const EcmParameters& params,
|
||||
std::vector<EntitledKeyInfo>* keys) {
|
||||
return ecm_gen_.ProcessEcmParameters(params, keys);
|
||||
}
|
||||
|
||||
int EntitlementKeySize(const EcmParameters& params) const {
|
||||
return params.entitlement_key_id.size();
|
||||
}
|
||||
|
||||
void SetTestConfig1(EcmParameters* params) {
|
||||
params->entitlement_key_id = kEntitlementKeySingle;
|
||||
params->rotation_enabled = false;
|
||||
params->key_params.push_back(kKeyParamsSingle);
|
||||
}
|
||||
|
||||
void SetTestConfig2(EcmParameters* params) {
|
||||
KeyParameters even_key_params;
|
||||
KeyParameters odd_key_params;
|
||||
params->entitlement_key_id = kEntitlementKeyDouble;
|
||||
params->rotation_enabled = true;
|
||||
params->rotation_periods = 2;
|
||||
params->key_params.push_back(kKeyParamsEven);
|
||||
params->key_params.push_back(kKeyParamsOdd);
|
||||
}
|
||||
|
||||
// Call this to setup the guts (CasEcm) of the ECM Generator.
|
||||
void PrepareEcmGenerator(bool key_rotation_enabled) {
|
||||
ecm_ = absl::make_unique<CasEcm>();
|
||||
std::string entitlement_request;
|
||||
std::string entitlement_response;
|
||||
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
|
||||
ecm_init_params_.track_types.push_back("SD");
|
||||
ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_,
|
||||
&entitlement_request));
|
||||
ASSERT_OK(
|
||||
HandleCasEncryptionRequest(entitlement_request, &entitlement_response));
|
||||
ASSERT_OK(ecm_->ProcessCasEncryptionResponse(entitlement_response));
|
||||
ecm_gen_.set_ecm(std::move(ecm_));
|
||||
}
|
||||
|
||||
const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle,
|
||||
kEcmKeyDataSingle,
|
||||
kEcmWrappedKeySingle,
|
||||
kEcmWrappedKeyIvSingle,
|
||||
{kEcmContentIvSingle}};
|
||||
const KeyParameters kKeyParamsEven{kEcmKeyIdEven,
|
||||
kEcmKeyDataEven,
|
||||
kEcmWrappedKeyEven,
|
||||
kEcmWrappedKeyIvEven,
|
||||
{kEcmContentIvEven}};
|
||||
const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd,
|
||||
kEcmKeyDataOdd,
|
||||
kEcmWrappedKeyOdd,
|
||||
kEcmWrappedKeyIvOdd,
|
||||
{kEcmContentIvOdd}};
|
||||
|
||||
std::unique_ptr<CasEcm> ecm_;
|
||||
EcmInitParameters ecm_init_params_;
|
||||
CasEcmGenerator ecm_gen_;
|
||||
};
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, InitializeNoRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
|
||||
PrepareEcmGenerator(false);
|
||||
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
ASSERT_OK(status);
|
||||
|
||||
ASSERT_EQ(EntitlementKeySize(ecm_params), 16);
|
||||
ASSERT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_FALSE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, GenerateNoRotation) {
|
||||
PrepareEcmGenerator(false);
|
||||
EcmParameters ecm_params;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Expected size (bytes):
|
||||
// CA system ID: 2 bytes
|
||||
// version: 1 byte
|
||||
// generation + flags: 1 byte
|
||||
// flags: 1 byte
|
||||
// entitlement key ID: 16 bytes
|
||||
// Single key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 77
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x02', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key =
|
||||
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
|
||||
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
|
||||
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, Generate2NoRotation) {
|
||||
PrepareEcmGenerator(false);
|
||||
EcmParameters ecm_params;
|
||||
|
||||
SetTestConfig1(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Second generate should have higher generation number.
|
||||
ASSERT_EQ(77, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x0A', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16));
|
||||
// Unwrap key and compare with original.
|
||||
std::string wrapping_key = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key =
|
||||
crypto_util::DecryptAesCbcNoPad(wrapping_key, wrapping_iv, wrapped_key);
|
||||
EXPECT_EQ(kEcmKeyDataSingle, unwrapped_key);
|
||||
EXPECT_EQ(kEcmContentIvSingle, ecm_string.substr(69, 8));
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, InitializeSimpleRotation) {
|
||||
EXPECT_FALSE(ecm_gen_.initialized());
|
||||
EcmParameters ecm_params;
|
||||
std::vector<EntitledKeyInfo> keys;
|
||||
|
||||
PrepareEcmGenerator(true);
|
||||
|
||||
SetTestConfig2(&ecm_params);
|
||||
ecm_init_params_.key_rotation_enabled = true;
|
||||
|
||||
util::Status status = ProcessEcmParameters(ecm_params, &keys);
|
||||
|
||||
EXPECT_TRUE(status.ok());
|
||||
EXPECT_TRUE(ecm_gen_.initialized());
|
||||
EXPECT_TRUE(ecm_gen_.rotation_enabled());
|
||||
}
|
||||
|
||||
TEST_F(CasEcmGeneratorTest, GenerateSimpleRotation) {
|
||||
EcmParameters ecm_params;
|
||||
|
||||
PrepareEcmGenerator(true);
|
||||
|
||||
SetTestConfig2(&ecm_params);
|
||||
|
||||
std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params);
|
||||
|
||||
// Expected size (bytes):
|
||||
// same as no rotation case = 77
|
||||
// second entitlement key ID: 16 bytes
|
||||
// Second key: ID (16), Data (16), IV (16), IV (8) = 56
|
||||
// total = 149
|
||||
ASSERT_EQ(149, ecm_string.size());
|
||||
EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte.
|
||||
EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte.
|
||||
EXPECT_EQ('\x01', ecm_string[2]); // ECM version
|
||||
EXPECT_EQ('\x03', ecm_string[3]); // generation + flags
|
||||
EXPECT_EQ('\x80', ecm_string[4]); // flags
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16));
|
||||
EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyEven, ecm_string.substr(37, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16));
|
||||
EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8));
|
||||
EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(77, 16));
|
||||
EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyOdd, ecm_string.substr(109, 16));
|
||||
EXPECT_NE(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16));
|
||||
EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8));
|
||||
// Unwrap even key and compare with original.
|
||||
std::string wrapping_key_even = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv_even = ecm_string.substr(53, 16);
|
||||
std::string wrapped_key_even = ecm_string.substr(37, 16);
|
||||
std::string unwrapped_key_even = crypto_util::DecryptAesCbcNoPad(
|
||||
wrapping_key_even, wrapping_iv_even, wrapped_key_even);
|
||||
EXPECT_EQ(kEcmKeyDataEven, unwrapped_key_even);
|
||||
// Unwrap odd key and compare with original.
|
||||
std::string wrapping_key_odd = kFakeCasEncryptionResponseKeyData;
|
||||
std::string wrapping_iv_odd = ecm_string.substr(125, 16);
|
||||
std::string wrapped_key_odd = ecm_string.substr(109, 16);
|
||||
std::string unwrapped_key_odd = crypto_util::DecryptAesCbcNoPad(
|
||||
wrapping_key_odd, wrapping_iv_odd, wrapped_key_odd);
|
||||
EXPECT_EQ(kEcmKeyDataOdd, unwrapped_key_odd);
|
||||
}
|
||||
|
||||
} // namespace cas
|
||||
} // namespace widevine
|
||||
Reference in New Issue
Block a user