Export media_cas_packager_sdk

This commit is contained in:
Fang Yu
2018-10-01 14:59:29 -07:00
parent 5bdf48b400
commit ba0d63e2c1
110 changed files with 14079 additions and 0 deletions

View 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",
],
)

View 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

View 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_

View 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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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(&params_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

View 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",
],
)

View 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

View 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_

View 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