Implement a set of "Simplified APIs" for ECM generation for castlabs.com.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=217601738
This commit is contained in:
Fang Yu
2018-10-17 15:38:59 -07:00
parent 08829edd17
commit fcdd9fa38c
17 changed files with 703 additions and 38 deletions

View File

@@ -16,12 +16,15 @@ package(
filegroup(
name = "binary_release_files",
srcs = [
"simulcrypt_client.cc",
"test_simulcrypt_messages.h",
":simulcrypt_client",
],
)
cc_library(
name = "constants",
hdrs = ["constants.h"],
)
cc_binary(
name = "simulcrypt_client",
srcs = ["simulcrypt_client.cc"],

30
example/constants.h Normal file
View File

@@ -0,0 +1,30 @@
////////////////////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////////////////////
// Some constant values for testing / demo purpose.
#ifndef MEDIA_CAS_PACKAGER_SDK_EXAMPLE_CONSTANTS_H_
#define MEDIA_CAS_PACKAGER_SDK_EXAMPLE_CONSTANTS_H_
namespace widevine {
namespace cas {
const char kDefaultContentId[] = "21140844";
const char kDefaultProvider[] = "widevine";
// Size of this IV needs to match ecm_init_params.content_iv_size.
const char kDefaultContentIv8Bytes[] = {'\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01'};
const char kDefaultContentIv16Bytes[] = {
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01', '\x01'};
const char kDefaultTrackTypeSd[] = "SD";
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_EXAMPLE_CONSTANTS_H_

View File

@@ -6,7 +6,7 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
// Example client that talks to server_main.
// Example client for demonstrating network calls to public/simulcrypt_server.
#include <errno.h>
#include <netdb.h>

View File

@@ -55,7 +55,6 @@ cc_library(
hdrs = ["ecm_generator.h"],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"//util:status",
"//media_cas_packager_sdk/internal:ecm",
],
@@ -91,6 +90,7 @@ cc_library(
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"//util:status",
"//example:constants",
],
)

View File

@@ -299,7 +299,12 @@ util::Status CasEcm::WrapEntitledKeys(
for (auto entitled_key : keys) {
entitled_key->entitlement_key_id = entitlement_key->key_id;
// Wrap key using entitlement key. First generate new IV.
// TODO(user): Do not randomly generate 'wrapped_key_iv' here ever,
// enforce the caller of 'ecm generator' to provide it.
// And check the provided 'wrapped_key_iv' has valid size.
if (entitled_key->wrapped_key_iv.empty()) {
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);

View File

@@ -75,6 +75,8 @@ struct EcmInitParameters {
class CasEcm {
public:
CasEcm() = default;
CasEcm(const CasEcm&) = delete;
CasEcm& operator=(const CasEcm&) = delete;
virtual ~CasEcm() = default;
// Perform initialization for a new ECM stream.

View File

@@ -64,6 +64,7 @@ util::Status CasEcmGenerator::ProcessEcmParameters(
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.wrapped_key_iv = ecm_params.key_params[i].wrapped_key_iv;
key.content_iv = ecm_params.key_params[i].content_ivs[0];
}
current_key_index_ = 0;

View File

@@ -59,6 +59,8 @@ struct EcmParameters {
class CasEcmGenerator {
public:
CasEcmGenerator() = default;
CasEcmGenerator(const CasEcmGenerator&) = delete;
CasEcmGenerator& operator=(const CasEcmGenerator&) = delete;
virtual ~CasEcmGenerator() = default;
virtual std::string GenerateEcm(const EcmParameters& params);

View File

@@ -203,7 +203,7 @@ TEST_F(CasEcmGeneratorTest, GenerateNoRotation) {
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));
EXPECT_EQ(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);
@@ -233,7 +233,7 @@ TEST_F(CasEcmGeneratorTest, Generate2NoRotation) {
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));
EXPECT_EQ(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);
@@ -284,12 +284,12 @@ TEST_F(CasEcmGeneratorTest, GenerateSimpleRotation) {
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(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(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;

View File

@@ -19,6 +19,7 @@
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
#include "example/constants.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "media_cas_packager_sdk/internal/ecmg_constants.h"
#include "media_cas_packager_sdk/internal/util.h"
@@ -28,13 +29,6 @@ namespace cas {
namespace {
static const char kDefaultContentId[] = "21140844";
static const char kDefaultProvider[] = "widevine";
// Size of this IV needs to match ecm_init_params.content_iv_size.
static const char kDefaultContentIv[] = {'\x01', '\x01', '\x01', '\x01',
'\x01', '\x01', '\x01', '\x01'};
static const char kDefaultTrackTypeSd[] = "SD";
// Local helper function that processes all the parameters in an ECMG message.
util::Status ProcessParameters(const char* message, size_t message_length,
EcmgParameters* parameters) {
@@ -271,7 +265,7 @@ util::Status Ecmg::ProcessCwProvisionMessage(const char* message,
ecm_param.key_params[i].key_id = ecm_param.key_params[i].key_data;
// TODO(user): MUST have a better way to generate/retrieve content_iv.
ecm_param.key_params[i].content_ivs.push_back(
std::string(kDefaultContentIv));
std::string(kDefaultContentIv8Bytes));
}
std::string serialized_ecm = ecm_generator.GenerateEcm(ecm_param);
std::cout << "serialized_ecm: " << serialized_ecm << std::endl;

View File

@@ -14,16 +14,6 @@
namespace widevine {
namespace cas {
namespace {
// Key IDs are 16 bytes, keys are 32 bytes.
static const char* kKeyId1 = "fake_key_id1....";
static const char* kKey1 = "fakefakefakefakefakefakefake1...";
static const char* kKeyId2 = "fake_key_id2....";
static const char* kKey2 = "fakefakefakefakefakefakefake2...";
} // namespace
util::Status FixedKeyFetcher::RequestEntitlementKey(
const std::string& request_string, std::string* signed_response_string) {
CasEncryptionRequest request;
@@ -36,20 +26,20 @@ util::Status FixedKeyFetcher::RequestEntitlementKey(
if (request.key_rotation()) {
// Add the Even key.
auto key = response.add_entitlement_keys();
key->set_key_id(kKeyId1);
key->set_key(kKey1);
key->set_key_id(even_entitlement_key_id_);
key->set_key(even_entitlement_key_);
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(kKeyId2);
key->set_key(kKey2);
key->set_key_id(odd_entitlement_key_id_);
key->set_key(odd_entitlement_key_);
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(kKeyId1);
key->set_key(kKey1);
key->set_key_id(even_entitlement_key_id_);
key->set_key(even_entitlement_key_);
key->set_track_type(track_type);
key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE);
}

View File

@@ -18,7 +18,26 @@ namespace cas {
// locally-constructed response that has known (predefined) entitlement keys.
class FixedKeyFetcher : public KeyFetcher {
public:
FixedKeyFetcher() {}
// Key IDs are 16 bytes, keys are 32 bytes.
// TODO(user): There should be a single entitlement key for both even
// and odd keys. Shouldn't have two different types of entitlement keys.
FixedKeyFetcher()
: even_entitlement_key_id_("fake_key_id1...."),
even_entitlement_key_("fakefakefakefakefakefakefake1..."),
odd_entitlement_key_id_("fake_key_id2...."),
odd_entitlement_key_("fakefakefakefakefakefakefake2...") {}
// Explictly provide the key_id and entitlement keys rather than using the
// hardcoded default.
FixedKeyFetcher(const std::string& even_entitlement_key_id,
const std::string& even_entitlement_key,
const std::string& odd_entitlement_key_id,
const std::string& odd_entitlement_key)
: even_entitlement_key_id_(even_entitlement_key_id),
even_entitlement_key_(even_entitlement_key),
odd_entitlement_key_id_(odd_entitlement_key_id),
odd_entitlement_key_(odd_entitlement_key) {}
FixedKeyFetcher(const FixedKeyFetcher&) = delete;
FixedKeyFetcher& operator=(const FixedKeyFetcher&) = delete;
~FixedKeyFetcher() override = default;
// Get entitlement keys. Process a CasEncryptionRequest message to
@@ -32,6 +51,12 @@ class FixedKeyFetcher : public KeyFetcher {
// WvCasEcm::ProcessCasEncryptionResponse().
util::Status RequestEntitlementKey(const std::string& request_string,
std::string* signed_response_string) override;
private:
std::string even_entitlement_key_id_;
std::string even_entitlement_key_;
std::string odd_entitlement_key_id_;
std::string odd_entitlement_key_;
};
} // namespace cas

View File

@@ -21,7 +21,6 @@ PUBLIC_COPTS = ["-fvisibility=default"]
filegroup(
name = "binary_release_files",
srcs = glob(["*.h"]) + [
"simulcrypt_server.cc",
":simulcrypt_server",
],
)
@@ -29,6 +28,7 @@ filegroup(
cc_binary(
name = "libmedia_cas_packager_sdk.so",
linkshared = 1,
deps = [":wv_cas_ecm"],
)
cc_library(
@@ -37,10 +37,11 @@ cc_library(
hdrs = glob(["*.h"]),
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"//util:status",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//protos/public:media_cas_encryption_proto",
"//protos/public:media_cas_proto",
],
)
@@ -49,5 +50,36 @@ cc_binary(
srcs = ["simulcrypt_server.cc"],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
],
)
cc_library(
name = "wv_cas_ecm",
srcs = ["wv_cas_ecm.cc"],
hdrs = ["wv_cas_ecm.h"],
copts = PUBLIC_COPTS,
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"//util:status",
"//example:constants",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
"//protos/public:media_cas_proto",
],
)
cc_test(
name = "wv_cas_ecm_test",
size = "small",
srcs = ["wv_cas_ecm_test.cc"],
deps = [
":wv_cas_ecm",
"//testing:gunit_main",
"//util:status",
],
)

View File

@@ -11,10 +11,8 @@
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>

View File

@@ -0,0 +1,236 @@
////////////////////////////////////////////////////////////////////////////////
// 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/wv_cas_ecm.h"
#include <stdio.h>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
#include "example/constants.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "protos/public/media_cas.pb.h"
namespace widevine {
namespace cas {
namespace {
static constexpr size_t kContentIvSizeBytes8 = 8;
static constexpr size_t kWrappedKeyIvSizeBytes = 16;
static constexpr size_t kCryptoModeCbc = 0;
static constexpr size_t kCryptoModeCtr = 1;
int EcmIvSizeToInt(EcmIvSize iv_size) {
if (iv_size == kIvSize8) {
return 8;
} else if (iv_size == kIvSize16) {
return 16;
} else {
return 0;
}
}
} // namespace
util::Status WvCasEcm::Initialize(int content_iv_size,
bool key_rotation_enabled, int crypto_mode) {
if (initialized_) {
return util::Status(
util::error::INTERNAL,
"Cannot initialize an instance of WvCasEcm more than once");
}
if (content_iv_size != kContentIvSizeBytes8) {
return util::Status(util::error::INVALID_ARGUMENT,
"Only support content_iv_size being 8 now");
}
ecm_init_params_.content_iv_size = kIvSize8;
ecm_init_params_.key_rotation_enabled = key_rotation_enabled;
if (crypto_mode != kCryptoModeCtr) {
return util::Status(util::error::INVALID_ARGUMENT,
"Only CTR crypto mode is supported by Widevine plugin "
"for content encryption");
}
ecm_init_params_.crypto_mode = CasCryptoMode::CTR;
// Internal ECM class can hold entitlement keys for multiple tracks.
// So we need to set a default track type here to be associated with
// the entitlement keys set later.
ecm_init_params_.track_types.push_back(kDefaultTrackTypeSd);
initialized_ = true;
return util::OkStatus();
}
util::Status WvCasEcm::GenerateEcm(const std::string& even_key,
const std::string& even_wrapping_iv,
const std::string& even_content_iv,
const std::string& odd_key,
const std::string& odd_wrapping_iv,
const std::string& odd_content_iv,
const std::string& entitlement_key_id,
const std::string& entitlement_key, std::string* ecm) {
DCHECK(ecm);
if (!initialized_) {
return util::Status(util::error::INTERNAL,
"WvCasEcm has not been properly initialized");
}
if (!ecm_init_params_.key_rotation_enabled) {
return util::Status(util::error::INTERNAL,
"Please call GenerateSingleKeyEcm() instead when key "
"rotation is disabled");
}
if (even_wrapping_iv.size() != kWrappedKeyIvSizeBytes ||
odd_wrapping_iv.size() != kWrappedKeyIvSizeBytes) {
return util::Status(util::error::INVALID_ARGUMENT,
"Size of wrapping IV is incorrect");
}
if (even_content_iv.size() !=
EcmIvSizeToInt(ecm_init_params_.content_iv_size) ||
odd_content_iv.size() !=
EcmIvSizeToInt(ecm_init_params_.content_iv_size)) {
return util::Status(util::error::INVALID_ARGUMENT,
"Size of content IV is incorrect");
}
// Create an instance of CasEcm in order to set the entitlement keys.
std::unique_ptr<CasEcm> cas_ecm = absl::make_unique<CasEcm>();
std::string entitlement_request;
std::string entitlement_response;
// 'content_id' and 'provider' are used in entitlement key request/response
// only, NOT needed for constructing the ECM. So we just use hardcoded value
// here for now.
// TODO(user): When we want to retrieve entitlement key from License Server
// we need to figure out a way to provide real 'content_id' and 'provder'
// to this function here.
util::Status status;
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
ecm_init_params_, &entitlement_request))
.ok()) {
return status;
}
FixedKeyFetcher fixed_key_fetcher(
/* even_entitlement_key_id= */ entitlement_key_id,
/* even_entitlement_key= */ entitlement_key,
/* odd_entitlement_key_id= */ entitlement_key_id,
/* odd_entitlement_key= */ entitlement_key);
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
&entitlement_response))
.ok()) {
return status;
}
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
.ok()) {
return status;
}
// Generate ECM.
CasEcmGenerator ecm_generator;
ecm_generator.set_ecm(std::move(cas_ecm));
EcmParameters ecm_param;
ecm_param.rotation_enabled = ecm_init_params_.key_rotation_enabled;
// Add even entitlement key.
ecm_param.key_params.emplace_back();
ecm_param.key_params[0].key_data = even_key;
ecm_param.key_params[0].wrapped_key_iv = even_wrapping_iv;
// Per 1:1 with hali@ just use entitlement_key_id for key_id here.
// TODO(user): Follow up with jfore@ why we need key_id in the ECM.
ecm_param.key_params[0].key_id = entitlement_key_id;
ecm_param.key_params[0].content_ivs.push_back(even_content_iv);
// Add odd entitlement key.
ecm_param.key_params.emplace_back();
ecm_param.key_params[1].key_data = odd_key;
ecm_param.key_params[1].wrapped_key_iv = odd_wrapping_iv;
ecm_param.key_params[1].key_id = entitlement_key_id;
ecm_param.key_params[1].content_ivs.push_back(odd_content_iv);
*ecm = ecm_generator.GenerateEcm(ecm_param);
return util::OkStatus();
}
util::Status WvCasEcm::GenerateSingleKeyEcm(const std::string& even_key,
const std::string& even_wrapping_iv,
const std::string& even_content_iv,
const std::string& entitlement_key_id,
const std::string& entitlement_key,
std::string* ecm) {
if (!initialized_) {
DCHECK(ecm);
return util::Status(util::error::INTERNAL,
"WvCasEcm has not been properly initialized");
}
if (ecm_init_params_.key_rotation_enabled) {
return util::Status(
util::error::INTERNAL,
"Please call GenerateEcm() instead when key rotation is enabled");
}
if (even_wrapping_iv.size() != kWrappedKeyIvSizeBytes) {
return util::Status(util::error::INVALID_ARGUMENT,
"Size of wrapping IV is incorrect");
}
if (even_content_iv.size() !=
EcmIvSizeToInt(ecm_init_params_.content_iv_size)) {
return util::Status(util::error::INVALID_ARGUMENT,
"Size of content IV is incorrect");
}
// Create an instance of CasEcm in order to set the entitlement keys.
std::unique_ptr<CasEcm> cas_ecm = absl::make_unique<CasEcm>();
std::string entitlement_request;
std::string entitlement_response;
// 'content_id' and 'provider' are used in entitlement key request/response
// only, NOT needed for constructing the ECM. So we just use hardcoded value
// here for now.
// TODO(user): When we want to retrieve entitlement key from License Server
// we need to figure out a way to provide real 'content_id' and 'provder'
// to this function here.
util::Status status;
if (!(status = cas_ecm->Initialize(kDefaultContentId, kDefaultProvider,
ecm_init_params_, &entitlement_request))
.ok()) {
return status;
}
FixedKeyFetcher fixed_key_fetcher(
/* even_entitlement_key_id= */ entitlement_key_id,
/* even_entitlement_key= */ entitlement_key,
/* odd_entitlement_key_id= */ "",
/* odd_entitlement_key= */ "");
if (!(status = fixed_key_fetcher.RequestEntitlementKey(entitlement_request,
&entitlement_response))
.ok()) {
return status;
}
if (!(status = cas_ecm->ProcessCasEncryptionResponse(entitlement_response))
.ok()) {
return status;
}
// Generate ECM.
CasEcmGenerator ecm_generator;
ecm_generator.set_ecm(std::move(cas_ecm));
EcmParameters ecm_param;
ecm_param.rotation_enabled = ecm_init_params_.key_rotation_enabled;
// Add even entitlement key.
ecm_param.key_params.emplace_back();
ecm_param.key_params[0].key_data = even_key;
ecm_param.key_params[0].wrapped_key_iv = even_wrapping_iv;
ecm_param.key_params[0].key_id = entitlement_key_id;
ecm_param.key_params[0].content_ivs.push_back(even_content_iv);
*ecm = ecm_generator.GenerateEcm(ecm_param);
return util::OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,128 @@
////////////////////////////////////////////////////////////////////////////////
// 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_WV_CAS_ECM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <stddef.h>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "util/status.h"
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "protos/public/media_cas.pb.h"
namespace widevine {
namespace cas {
// Class for generating Widevine CAS ECMs.
// See wv_cas_ecm_test.cc for example usage.
// This class is NOT thread-safe.
class WvCasEcm {
public:
WvCasEcm() = default;
WvCasEcm(const WvCasEcm&) = delete;
WvCasEcm& operator=(const WvCasEcm&) = delete;
virtual ~WvCasEcm() = default;
// Initialize an instance of this class.
//
// Args:
// - |content_iv_size| iv size in bytes for encrypting the content,
// only support 8 bytes now
// TODO(user): Double-check with jfore@ regarding only support 8 bytes.
// - |key_rotation_enabled| whether key rotation is enabled,
// if this is 'true' only subsequent call to GenerateEcm will be allowed,
// if this is 'false' only subsequent call to GenerateSingleKeyEcm will
// be allowed
// - |crypto_mode| crypto mode for encrypting content,
// kCryptoModeCbc = 0, kCryptoModeCtr = 1
// only CTR is supported by Widevine CAS plugin for now
// TODO(user): Check with jfore@ regarding supporting kCryptoModeCbc.
//
// Returns:
// - A status indicating whether there was any error during initialization
//
// Note:
// - 'even'/'odd' key in the ECM will be be encrypted using AEC_CBC
util::Status Initialize(int content_iv_size, bool key_rotation_enabled,
int crypto_mode);
// Generate an ECM containing two keys (even and odd). Can be called when
// |key_rotation_enabled| is initialized to 'true'.
//
// Args:
// - |even_key| clear even content key
// - |even_wrapping_iv| iv used when encrypting |even_key| in the ECM
// - |even_content_iv| iv used along with |even_key| for encrypting content
// - |odd_key| clear odd content key
// - |odd_wrapping_iv| iv used when encrypting |odd_key| in the ECM
// - |odd_content_iv| iv used along with |odd_key| for encrypting content
// - |entitlement_key_id| key id for |entitlement_key|
// - |entitlement_key| entitlement key used to encrypt even and odd keys
// - |ecm| for returning the generated ECM, must not be nullptr
//
// Returns:
// - A status indicating whether there was any error during processing
//
// Note:
// - The same |entitlement_key| will be used to encrypt both |even_key|
// and |odd_key| in the ECM
// - Currently, we only allow |even_content_iv| and |odd_content_iv|
// to be of size 8 bytes, so we assume the encryptor would append suffix
// {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}
// to |even_content_iv| and |odd_content_iv| to obtain a 16 bytes IV
// before encrypting the content
util::Status GenerateEcm(const std::string& even_key,
const std::string& even_wrapping_iv,
const std::string& even_content_iv, const std::string& odd_key,
const std::string& odd_wrapping_iv,
const std::string& odd_content_iv,
const std::string& entitlement_key_id,
const std::string& entitlement_key, std::string* ecm);
// Generate an ECM containing only a singe even key. Can be called when
// |key_rotation_enabled| is initialized to 'false'.
//
// Args:
// - |even_key| clear even content key
// - |even_wrapping_iv| iv used when encrypting |even_key| in the ECM
// - |even_content_iv| iv used along with |even_key| for encrypting content
// - |entitlement_key_id| key id for |entitlement_key|
// - |entitlement_key| entitlement key used to encrypt even key
// - |ecm| for returning the generated ECM, must not be nullptr
//
// Returns:
// - A status indicating whether there was any error during processing
//
// Note:
//
// - Currently, we only allow |even_content_iv|
// to be of size 8 bytes, so we assume the encryptor would append suffix
// {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}
// to |even_content_iv| to obtain a 16 bytes IV
// before encrypting the content
util::Status GenerateSingleKeyEcm(const std::string& even_key,
const std::string& even_wrapping_iv,
const std::string& even_content_iv,
const std::string& entitlement_key_id,
const std::string& entitlement_key, std::string* ecm);
private:
bool initialized_ = false;
EcmInitParameters ecm_init_params_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_

View File

@@ -0,0 +1,219 @@
////////////////////////////////////////////////////////////////////////////////
// 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/wv_cas_ecm.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
using ::testing::Test;
namespace widevine {
namespace cas {
const char kEvenKey[] = "even_content_key"; // 16 bytes
const char kEvenWrappingIv[] = "even_wrap_iv...."; // 16 bytes
const char kEvenContentIv[] = "evencont"; // 8 bytes
const char kOddKey[] = "odd_content_key."; // 16 bytes
const char kOddWrappingIv[] = "odd_wrap_iv....."; // 16 bytes
const char kOddContentIv[] = "oddcont."; // 8 bytes
const char kEntitlementKeyId[] = "ent_key_id......"; // 16 bytes
const char kEntitlementKey[] = "entitlement_key................."; // 32 bytes
class WvCasEcmTest : public Test {
protected:
WvCasEcmTest() {}
WvCasEcm wv_cas_ecm_;
};
TEST_F(WvCasEcmTest, InitializeTwice) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1));
EXPECT_EQ(util::error::INTERNAL,
wv_cas_ecm_
.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1)
.error_code());
}
TEST_F(WvCasEcmTest, InitializeContentIvSize) {
EXPECT_EQ(
util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.Initialize(/* content_iv_size= */ 16,
/* key_rotation_enabled= */ true, /* crypto_mode= */ 1)
.error_code());
}
TEST_F(WvCasEcmTest, InitializeCryptoMode) {
EXPECT_EQ(
util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true, /* crypto_mode= */ 0)
.error_code());
}
TEST_F(WvCasEcmTest, InitializeKeyRotationEnabled) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INTERNAL,
wv_cas_ecm_.GenerateSingleKeyEcm("", "", "", "", "", &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, InitializeKeyRotationDisabled) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INTERNAL,
wv_cas_ecm_.GenerateEcm("", "", "", "", "", "", "", "", &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateEcmInvalidWrappingIv) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ kEvenWrappingIv,
/* even_content_iv= */ kEvenContentIv,
/* odd_key= */ kOddKey,
/* odd_wrapping_iv= */ "12345678",
/* odd_content_iv= */ kOddContentIv,
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateEcmInvalidContentIv) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ kEvenWrappingIv,
/* even_content_iv= */ kEvenContentIv,
/* odd_key= */ kOddKey,
/* odd_wrapping_iv= */ kOddWrappingIv,
/* odd_content_iv= */ "123456789",
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcmInvalidWrappingIv) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ "12345678",
/* even_content_iv= */ kEvenContentIv,
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcmInvalidContentIv) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_EQ(util::error::INVALID_ARGUMENT,
wv_cas_ecm_
.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ kEvenWrappingIv,
/* even_content_iv= */ "1234",
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm)
.error_code());
}
TEST_F(WvCasEcmTest, GenerateEcmSuccess) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ true,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_OK(wv_cas_ecm_.GenerateEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ kEvenWrappingIv,
/* even_content_iv= */ kEvenContentIv,
/* odd_key= */ kOddKey,
/* odd_wrapping_iv= */ kOddWrappingIv,
/* odd_content_iv= */ kOddContentIv,
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm));
// 149 bytes.
char expected_ecm[] = {
'\x4a', '\xd4', '\x1', '\x3', '\x80', '\x65', '\x6e', '\x74', '\x5f',
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e',
'\x2e', '\x2e', '\x2e', '\x65', '\x6e', '\x74', '\x5f', '\x6b', '\x65',
'\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e', '\x2e', '\x2e',
'\x2e', '\x1d', '\xb6', '\x2f', '\x14', '\x1f', '\xa3', '\xd6', '\x68',
'\xd8', '\x79', '\xfe', '\x69', '\x5f', '\x3c', '\xad', '\x8b', '\x65',
'\x76', '\x65', '\x6e', '\x5f', '\x77', '\x72', '\x61', '\x70', '\x5f',
'\x69', '\x76', '\x2e', '\x2e', '\x2e', '\x2e', '\x65', '\x76', '\x65',
'\x6e', '\x63', '\x6f', '\x6e', '\x74', '\x65', '\x6e', '\x74', '\x5f',
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e',
'\x2e', '\x2e', '\x2e', '\x65', '\x6e', '\x74', '\x5f', '\x6b', '\x65',
'\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e', '\x2e', '\x2e',
'\x2e', '\xcb', '\xb2', '\x4d', '\x7d', '\xd1', '\x27', '\x97', '\xd3',
'\xf7', '\xe0', '\x11', '\x93', '\xa3', '\x43', '\xd9', '\x55', '\x6f',
'\x64', '\x64', '\x5f', '\x77', '\x72', '\x61', '\x70', '\x5f', '\x69',
'\x76', '\x2e', '\x2e', '\x2e', '\x2e', '\x2e', '\x6f', '\x64', '\x64',
'\x63', '\x6f', '\x6e', '\x74', '\x2e'};
EXPECT_EQ(std::string(expected_ecm, sizeof(expected_ecm)), actual_ecm);
}
TEST_F(WvCasEcmTest, GenerateSingleKeyEcmSuccess) {
EXPECT_OK(wv_cas_ecm_.Initialize(/* content_iv_size= */ 8,
/* key_rotation_enabled= */ false,
/* crypto_mode= */ 1));
std::string actual_ecm;
EXPECT_OK(wv_cas_ecm_.GenerateSingleKeyEcm(
/* even_key= */ kEvenKey,
/* even_wrapping_iv= */ kEvenWrappingIv,
/* even_content_iv= */ kEvenContentIv,
/* entitlement_key_id= */ kEntitlementKeyId,
/* entitlement_key= */ kEntitlementKey, &actual_ecm));
// 77 bytes.
char expected_ecm[] = {
'\x4a', '\xd4', '\x1', '\x2', '\x80', '\x65', '\x6e', '\x74', '\x5f',
'\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e',
'\x2e', '\x2e', '\x2e', '\x65', '\x6e', '\x74', '\x5f', '\x6b', '\x65',
'\x79', '\x5f', '\x69', '\x64', '\x2e', '\x2e', '\x2e', '\x2e', '\x2e',
'\x2e', '\x1d', '\xb6', '\x2f', '\x14', '\x1f', '\xa3', '\xd6', '\x68',
'\xd8', '\x79', '\xfe', '\x69', '\x5f', '\x3c', '\xad', '\x8b', '\x65',
'\x76', '\x65', '\x6e', '\x5f', '\x77', '\x72', '\x61', '\x70', '\x5f',
'\x69', '\x76', '\x2e', '\x2e', '\x2e', '\x2e', '\x65', '\x76', '\x65',
'\x6e', '\x63', '\x6f', '\x6e', '\x74'};
EXPECT_EQ(std::string(expected_ecm, sizeof(expected_ecm)), actual_ecm);
}
} // namespace cas
} // namespace widevine