diff --git a/example/BUILD b/example/BUILD index 6cee210..6a27142 100644 --- a/example/BUILD +++ b/example/BUILD @@ -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"], diff --git a/example/constants.h b/example/constants.h new file mode 100644 index 0000000..9628556 --- /dev/null +++ b/example/constants.h @@ -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_ diff --git a/example/simulcrypt_client.cc b/example/simulcrypt_client.cc index f2a6979..1df3822 100644 --- a/example/simulcrypt_client.cc +++ b/example/simulcrypt_client.cc @@ -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 #include diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index 1ed3cda..a2219a3 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -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", ], ) diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index b1c1991..8924cc2 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -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. - CHECK(RandomBytes(kWrappedKeyIvSizeBytes, &entitled_key->wrapped_key_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); diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index 51a9f40..349965b 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -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. diff --git a/media_cas_packager_sdk/internal/ecm_generator.cc b/media_cas_packager_sdk/internal/ecm_generator.cc index 83d670a..907af1b 100644 --- a/media_cas_packager_sdk/internal/ecm_generator.cc +++ b/media_cas_packager_sdk/internal/ecm_generator.cc @@ -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; diff --git a/media_cas_packager_sdk/internal/ecm_generator.h b/media_cas_packager_sdk/internal/ecm_generator.h index 8489dc2..8de6801 100644 --- a/media_cas_packager_sdk/internal/ecm_generator.h +++ b/media_cas_packager_sdk/internal/ecm_generator.h @@ -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); diff --git a/media_cas_packager_sdk/internal/ecm_generator_test.cc b/media_cas_packager_sdk/internal/ecm_generator_test.cc index 43dbd19..ea21b3e 100644 --- a/media_cas_packager_sdk/internal/ecm_generator_test.cc +++ b/media_cas_packager_sdk/internal/ecm_generator_test.cc @@ -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; diff --git a/media_cas_packager_sdk/internal/ecmg.cc b/media_cas_packager_sdk/internal/ecmg.cc index 46c6248..d893f13 100644 --- a/media_cas_packager_sdk/internal/ecmg.cc +++ b/media_cas_packager_sdk/internal/ecmg.cc @@ -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; diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc index f65e24c..2624465 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc @@ -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); } diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.h b/media_cas_packager_sdk/internal/fixed_key_fetcher.h index 6a7bc3e..4a079a6 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.h +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.h @@ -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 diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index 7820717..cc7baad 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -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", ], ) diff --git a/media_cas_packager_sdk/public/simulcrypt_server.cc b/media_cas_packager_sdk/public/simulcrypt_server.cc index 96321e3..4dac5ae 100644 --- a/media_cas_packager_sdk/public/simulcrypt_server.cc +++ b/media_cas_packager_sdk/public/simulcrypt_server.cc @@ -11,10 +11,8 @@ #include #include #include -#include #include #include -#include #include #include diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc new file mode 100644 index 0000000..79a1f20 --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -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 +#include +#include +#include +#include + +#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 cas_ecm = absl::make_unique(); + 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 cas_ecm = absl::make_unique(); + 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 diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h new file mode 100644 index 0000000..54695ab --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -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 +#include +#include +#include +#include +#include + +#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_ diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc new file mode 100644 index 0000000..7412c6f --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -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