From 6e1f37732923d13e5db57135fe63213dfccf0684 Mon Sep 17 00:00:00 2001 From: Fang Yu Date: Tue, 15 Jan 2019 12:50:30 -0800 Subject: [PATCH] Create some utility types/functions to help partners to create CasEncryptionRequest in JSON format and process CasEncryptionResponse in JSON format. The idea is that partner can take the CasEncryptionRequest in JSON to construct a signed license request, send it to Widevine license service (using whatever tool they have); and once they have a response, they can use another utility here to parse and understand what is in the response JSON. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=229422648 --- media_cas_packager_sdk/public/BUILD | 6 +- media_cas_packager_sdk/public/wv_cas_types.cc | 64 +++++++++++++++++++ media_cas_packager_sdk/public/wv_cas_types.h | 60 +++++++++++++++++ .../public/wv_cas_types_test.cc | 52 +++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index a4303b3..1c60d69 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -154,7 +154,11 @@ cc_library( srcs = ["wv_cas_types.cc"], hdrs = ["wv_cas_types.h"], copts = PUBLIC_COPTS, - deps = ["//base"], + deps = [ + "//base", + "//protos/public:media_cas_encryption_proto", + "//common:status", + ], ) cc_test( diff --git a/media_cas_packager_sdk/public/wv_cas_types.cc b/media_cas_packager_sdk/public/wv_cas_types.cc index 11c12e0..2464112 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.cc +++ b/media_cas_packager_sdk/public/wv_cas_types.cc @@ -10,6 +10,13 @@ #include "glog/logging.h" #include "base/macros.h" +#include "google/protobuf/util/json_util.h" +#include "common/status.h" +#include "protos/public/media_cas_encryption.pb.h" + +using google::protobuf::util::JsonPrintOptions; +using google::protobuf::util::JsonStringToMessage; +using google::protobuf::util::MessageToJsonString; namespace widevine { namespace cas { @@ -72,5 +79,62 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) { return false; } +Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request, + std::string* request_json) { + CHECK(request_json); + + CasEncryptionRequest request_proto; + request_proto.set_content_id(request.content_id); + request_proto.set_provider(request.provider); + for (const std::string& track_type : request.track_types) { + request_proto.add_track_types(track_type); + } + request_proto.set_key_rotation(request.key_rotation); + + JsonPrintOptions print_options; + // Set this option so that the json output is + // {"content_id":"MjExNDA4NDQ=", ... + // instead of + // {"contentId":"MjExNDA4NDQ=", ... + print_options.preserve_proto_field_names = true; + // NOTE: MessageToJsonString will automatically converts 'bytes' type fields + // to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='. + if (!MessageToJsonString(request_proto, request_json, print_options).ok()) { + return Status(error::INTERNAL, + "Failed to convert request message to json."); + } + + return OkStatus(); +} + +Status ParseWvCasEncryptionResponseJson(const std::string& response_json, + WvCasEncryptionResponse* response) { + CHECK(response); + + CasEncryptionResponse response_proto; + // NOTE: JsonStringToMessage will automatically perform base64 decode for + // 'bytes' type fields. + if (!JsonStringToMessage(response_json, &response_proto).ok()) { + return Status(error::INTERNAL, + "Failed to convert response json to message."); + } + + response->status = + static_cast(response_proto.status()); + response->status_message = response_proto.status_message(); + response->content_id = response_proto.content_id(); + for (const auto& key_info_proto : response_proto.entitlement_keys()) { + WvCasEncryptionResponse::KeyInfo key_info; + key_info.key_id = key_info_proto.key_id(); + key_info.key = key_info_proto.key(); + key_info.track_type = key_info_proto.track_type(); + key_info.key_slot = static_cast( + key_info_proto.key_slot()); + response->entitlement_keys.push_back(key_info); + } + + return OkStatus(); +} + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index 3acd30e..2d42894 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -10,6 +10,9 @@ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_TYPES_H_ #include +#include + +#include "common/status.h" namespace widevine { namespace cas { @@ -64,6 +67,63 @@ bool CryptoModeToString(CryptoMode mode, std::string* str); // Returns false if str is not a valid CryptoMode. bool StringToCryptoMode(const std::string& str, CryptoMode* mode); +struct WvCasEncryptionRequest { + std::string content_id; + std::string provider; + // Track types such as "AUDIO", SD" or "HD". + std::vector track_types; + // Indicates if the client is using key rotation. If true, the server will + // return one key for EVEN and one key for ODD, otherwise only a single key is + // returned. + bool key_rotation = true; +}; + +struct WvCasEncryptionResponse { + enum class Status { + STATUS_UNSPECIFIED = 0, + OK = 1, + SIGNATURE_FAILED = 2, + ACCESS_DENIED = 3, + INTERNAL_ERROR = 4, + INVALID_ARGUMENT = 5, + PROVIDER_ID_MISSING = 6, + CONTENT_ID_MISSING = 7, + TRACK_TYPE_MISSING = 8 + }; + struct KeyInfo { + enum class KeySlot { + KEY_SLOT_UNSPECIFIED = 0, + SINGLE = 1, + EVEN = 2, + ODD = 3 + }; + std::string key_id; + std::string key; + // Optional label used for the key. + std::string track_type; + KeySlot key_slot; + }; + Status status; + std::string status_message; + std::string content_id; + std::vector entitlement_keys; +}; + +// Returns a WvCasEncryptionRequest in JSON format. +// This request JSON can be later put into the 'request' field of a signed +// request JSON message. +// And that signed JSON message can be sent to Widevine license server for +// aquiring entitlement keys. +Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request, + std::string* request_json); + +// Parses a WvCasEncryptionResponse in JSON format, returns a +// WvCasEncryptionResponse. +// |response_json| is supposed to be the 'response' field in the signed +// response from Widevine license server. +Status ParseWvCasEncryptionResponseJson(const std::string& response_json, + WvCasEncryptionResponse* response); + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_types_test.cc b/media_cas_packager_sdk/public/wv_cas_types_test.cc index 6068e82..a12e4da 100644 --- a/media_cas_packager_sdk/public/wv_cas_types_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_types_test.cc @@ -8,6 +8,7 @@ #include "media_cas_packager_sdk/public/wv_cas_types.h" +#include "testing/gmock.h" #include "testing/gunit.h" namespace widevine { @@ -46,5 +47,56 @@ TEST(WvCasTypesTest, StringToCryptoMode) { EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode)); } +TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) { + WvCasEncryptionRequest request; + request.content_id = "test_content_id"; + request.provider = "test_provider"; + request.track_types.push_back("SD"); + request.track_types.push_back("AUDIO"); + request.key_rotation = true; + + std::string actual_request_json; + EXPECT_OK(CreateWvCasEncryptionRequestJson(request, &actual_request_json)); + + // Content_id has been base64 encoded. + std::string expected_request_json = + "{\"content_id\":\"dGVzdF9jb250ZW50X2lk\",\"provider\":\"test_provider\"," + "\"track_types\":[\"SD\",\"AUDIO\"],\"key_rotation\":true}"; + EXPECT_EQ(expected_request_json, actual_request_json); +} + +TEST(WvCasTypesTest, ParseWvCasEncryptionResponseJson) { + std::string response_json = + "{\"status\":\"OK\",\"content_id\":\"MjExNDA4NDQ=\",\"entitlement_keys\":" + "[{\"key_id\":\"ZmFrZV9rZXlfaWQxLi4uLg==\",\"key\":" + "\"ZmFrZWZha2VmYWtlZmFrZWZha2VmYWtlZmFrZTEuLi4=\"," + "\"track_type\":\"SD\",\"key_slot\":\"EVEN\"},{\"key_id\":" + "\"ZmFrZV9rZXlfaWQyLi4uLg==\",\"key\":" + "\"ZmFrZWZha2VmYWtlZmFrZWZha2VmYWtlZmFrZTIuLi4=\",\"track_type\":\"SD\"," + "\"key_slot\":\"ODD\"}]}"; + + WvCasEncryptionResponse actual_response; + EXPECT_OK(ParseWvCasEncryptionResponseJson(response_json, &actual_response)); + + EXPECT_EQ(WvCasEncryptionResponse::Status::OK, actual_response.status); + // 21140844 is base64 decode of "MjExNDA4NDQ=". + EXPECT_EQ("21140844", actual_response.content_id); + ASSERT_EQ(2, actual_response.entitlement_keys.size()); + // Base64 decode of the key_id in the json. + EXPECT_EQ("fake_key_id1....", actual_response.entitlement_keys.at(0).key_id); + // Base64 decode of the key in the json. + EXPECT_EQ("fakefakefakefakefakefakefake1...", + actual_response.entitlement_keys.at(0).key); + EXPECT_EQ("SD", actual_response.entitlement_keys.at(0).track_type); + EXPECT_EQ(WvCasEncryptionResponse::KeyInfo::KeySlot::EVEN, + actual_response.entitlement_keys.at(0).key_slot); + EXPECT_EQ("fake_key_id2....", actual_response.entitlement_keys.at(1).key_id); + EXPECT_EQ("fakefakefakefakefakefakefake2...", + actual_response.entitlement_keys.at(1).key); + EXPECT_EQ("SD", actual_response.entitlement_keys.at(1).track_type); + EXPECT_EQ(WvCasEncryptionResponse::KeyInfo::KeySlot::ODD, + actual_response.entitlement_keys.at(1).key_slot); +} + } // namespace cas } // namespace widevine