diff --git a/common/certificate_type.h b/common/certificate_type.h new file mode 100644 index 0000000..b63a639 --- /dev/null +++ b/common/certificate_type.h @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2017 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 COMMON_CERTIFICATE_TYPE_H_ +#define COMMON_CERTIFICATE_TYPE_H_ + +namespace widevine { + +enum CertificateType { + kCertificateTypeTesting, + kCertificateTypeDevelopment, + kCertificateTypeProduction, +}; + +} // namespace widevine + +#endif // COMMON_CERTIFICATE_TYPE_H_ diff --git a/common/status.h b/common/status.h new file mode 100644 index 0000000..28d5a86 --- /dev/null +++ b/common/status.h @@ -0,0 +1,116 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2017 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 COMMON_STATUS_H_ +#define COMMON_STATUS_H_ + +#include +#include + +#include "util/error_space.h" + +namespace widevine { +namespace error { + +enum StatusCode { + // Success. + OK = 0, + + // Client specified an invalid argument. + INVALID_ARGUMENT = 3, + + // Some requested entity (e.g., file or directory) was not found. + NOT_FOUND = 5, + + // Some entity that we attempted to create (e.g., file or directory) + // already exists. + ALREADY_EXISTS = 6, + + // The caller does not have permission to execute the specified + // operation. PERMISSION_DENIED must not be used for rejections + // caused by exhausting some resource (use RESOURCE_EXHAUSTED + // instead for those errors). + PERMISSION_DENIED = 7, + + // The operation was rejected because the system is not in a state + // required for the operation's execution. For example, the directory + // to be deleted is non-empty, an rmdir operation is applied to + // a non-directory, etc. + FAILED_PRECONDITION = 9, + + // Operation is not implemented or not supported/enabled in this service. + UNIMPLEMENTED = 12, + + // Internal errors. Means some invariants expected by underlying + // system has been broken. If you see one of these errors, + // something is very broken. + INTERNAL = 13, + + // Operation is not implemented or not supported/enabled in this service. + UNAVAILABLE = 14, + + // Number of generic (non license related) errors. + NUM_ERRORS, +}; + +} // namespace error + +class Status { + public: + + Status() = default; + + ~Status() = default; + + explicit Status(error::StatusCode c) : status_code_(c) {} + + Status(error::StatusCode c, const std::string& error_message) + : status_code_(c), error_message_(error_message) {} + + Status(const util::ErrorSpace* e, error::StatusCode c, + const std::string& error_message) + : error_space_(e), status_code_(c), error_message_(error_message) {} + + Status(const util::ErrorSpace* e, int error, + const std::string& error_message) + : error_space_(e), status_code_(error), error_message_(error_message) {} + + bool ok() const { return status_code_ == error::OK; } + const util::ErrorSpace* error_space() const { return error_space_; } + static const util::ErrorSpace* canonical_space(); + std::string ToString() const; + std::string error_message() const { return error_message_; } + int error_code() const { return status_code_; } + + private: + const util::ErrorSpace* error_space_ = canonical_space(); + int status_code_ = error::OK; + std::string error_message_; +}; + +inline Status OkStatus() { return Status(); } + +inline bool operator==(const Status& s1, const Status& s2) { + return s1.error_space() == s2.error_space() && + s1.error_code() == s2.error_code() && + s1.error_message() == s2.error_message(); +} +inline bool operator!=(const Status& s1, const Status& s2) { + return !(s1 == s2); +} + + +// Prints a human-readable representation of 'x' to 'os'. +std::ostream& operator<<(std::ostream& os, const Status& x); + +#define CHECK_OK(expression) CHECK(expression.ok()) << expression.ToString() + + +} // namespace widevine + +#endif // COMMON_STATUS_H_ diff --git a/example/test_ecmg_messages.h b/example/test_ecmg_messages.h index 77a018e..6b81fa9 100644 --- a/example/test_ecmg_messages.h +++ b/example/test_ecmg_messages.h @@ -27,43 +27,44 @@ constexpr char kTestEcmgChannelSetup[] = { }; constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = { - '\x03', // protocol_version - '\x00', '\x01', // message_type - Channel_setup - '\x00', '\x70', // message_length - '\x00', '\x0e', // parameter_type - ECM_channel_id - '\x00', '\x02', // parameter_length - '\x00', '\x01', // parameter_value - '\x00', '\x01', // parameter_type- SUPER_CAS_ID - '\x00', '\x04', // parameter_length - '\x4a', '\xd4', '\x00', '\x00', // parameter_value - '\x80', '\x00', // parameter_type - AGE_RESTRICTION - '\x00', '\x01', // parameter_length - '\x00', // parameter_value - '\x80', '\x01', // parameter_type - CRYPTO_MODE - '\x00', '\x07', // parameter_length - 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value - '\x80', '\x04', // parameter_type - TRACK_TYPES - '\x00', '\x02', // parameter_length - 'S', 'D', // parameter_value - '\x80', '\x04', // parameter_type - TRACK_TYPES - '\x00', '\x02', // parameter_length - 'H', 'D', // parameter_value - '\x80', '\x02', // parameter_type - CONTENT_ID - '\x00', '\x09', // parameter_length - 'C', 'a', 's', 'T', 's', 'F', 'a', 'k', - 'e', // parameter_value - CasTsFake - '\x80', '\x03', // parameter_type - CONTENT_PROVIDER - '\x00', '\x0d', // parameter_length - 'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e', - '_', 't', 'e', 's', 't', // parameter_value - widevine_test - '\x80', '\x06', // parameter_type - CONTENT_IV - '\x00', '\x10', // parameter_length - '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', - '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', - '\x80', '\x06', // parameter_type - CONTENT_IV - '\x00', '\x10', // parameter_length - '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', - '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; + '\x03', // protocol_version + '\x00', '\x01', // message_type - Channel_setup + '\x00', '\x8c', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x01', // parameter_type- SUPER_CAS_ID + '\x00', '\x04', // parameter_length + '\x4a', '\xd4', '\x00', '\x00', // parameter_value + '\x80', '\x00', // parameter_type - AGE_RESTRICTION + '\x00', '\x01', // parameter_length + '\x00', // parameter_value + '\x80', '\x01', // parameter_type - CRYPTO_MODE + '\x00', '\x07', // parameter_length + 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value + '\x80', '\x04', // parameter_type - TRACK_TYPES + '\x00', '\x02', // parameter_length + 'S', 'D', // parameter_value + '\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION + '\x00', '\x30', // parameter_length + // parameter_value - ENTITLEMENT_ID (16 bytes) + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + // parameter_value (continued) - ENTITLEMENT_KEY (32 bytes) + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION + '\x00', '\x30', // parameter_length + // parameter_value - ENTITLEMENT_ID (16 bytes) + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + // parameter_value (continued) - ENTITLEMENT_KEY (32 bytes) + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'}; constexpr char kTestEcmgChannelTest[] = { '\x03', // protocol_version @@ -74,6 +75,18 @@ constexpr char kTestEcmgChannelTest[] = { '\x00', '\x01', // parameter_value }; +constexpr char kTestEcmgChannelError[] = { + '\x03', // protocol_version + '\x00', '\x05', // message_type - Stream_error + '\x00', '\x0c', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x70', '\x00', // parameter_type - Error_status + '\x00', '\x02', // parameter_length + '\x70', '\x00', // parameter_value (UNKNOWN_ERROR) +}; + constexpr char kTestEcmgChannelStatus[] = { '\x03', // protocol_version '\x00', '\x03', // message_type - Channel_status @@ -113,7 +126,7 @@ constexpr char kTestEcmgChannelStatus[] = { constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = { '\x03', // protocol_version '\x01', '\x01', // message_type - Stream_setup - '\x00', '\x1e', // message_length + '\x00', '\x46', // message_length '\x00', '\x0e', // parameter_type - ECM_channel_id '\x00', '\x02', // parameter_length '\x00', '\x01', // parameter_value @@ -129,7 +142,14 @@ constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = { '\x80', '\x05', // parameter_type - STREAM_TRACK_TYPE '\x00', '\x02', // parameter_length 'S', 'D', // parameter_value -}; + '\x80', '\x06', // parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x80', '\x06', // parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; constexpr char kTestEcmgStreamSetup[] = { '\x03', // protocol_version @@ -179,6 +199,24 @@ constexpr char kTestEcmgStreamStatus[] = { '\x01' // parameter_value }; +constexpr char kTestEcmgStreamError[] = { + '\x03', // protocol_version + '\x01', '\x06', // message_type - Stream_error + '\x00', '\x1a', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x70', '\x00', // parameter_type - Error_status + '\x00', '\x02', // parameter_length + '\x70', '\x00', // parameter_value (UNKNOWN_ERROR) + '\x70', '\x01', // parameter_type - Error_information + '\x00', '\x04', // parameter_length + 'i', 'n', 'f', 'o' // parameter_value +}; + constexpr char kTestEcmgCwProvision[] = { '\x03', // protocol_version '\x02', '\x01', // message_type - CW_provision @@ -209,7 +247,7 @@ constexpr char kTestEcmgCwProvision[] = { constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = { '\x03', // protocol_version '\x02', '\x01', // message_type - CW_provision - '\x00', '\xaa', // message_length + '\x00', '\xf4', // message_length '\x00', '\x0e', // parameter_type - ECM_channel_id '\x00', '\x02', // parameter_length '\x00', '\x01', // parameter_value @@ -225,43 +263,55 @@ constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = { '\x00', '\x14', // parameter_type - CP_CW_Combination '\x00', '\x12', // parameter_length '\x00', '\x00', // parameter_value - CP (2 bytes) then CW next (16 bytes) - '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', - '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', // + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', // '\x00', '\x14', // parameter_type - CP_CW_Combination '\x00', '\x12', // parameter_length '\x00', '\x01', // parameter_value - CP (2 bytes) then CW next (16 bytes) - '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', - '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', // + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', // '\x00', '\x0d', // parameter_type - access_criteria - '\x00', '\x62', // parameter_length + '\x00', '\xac', // parameter_length '\x80', '\x00', // access_criteria parameter_type - AGE_RESTRICTION '\x00', '\x01', // parameter_length '\x00', // parameter_value '\x80', '\x01', // access_criteria parameter_type - CRYPTO_MODE '\x00', '\x07', // parameter_length - 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value + 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value '\x80', '\x04', // access_criteria parameter_type - TRACK_TYPES '\x00', '\x02', // parameter_length - 'S', 'D', // parameter_value + 'S', 'D', // parameter_value '\x80', '\x05', // access_criteria parameter_type - STREAM_TRACK_TYPE '\x00', '\x02', // parameter_length - 'S', 'D', // parameter_value - '\x80', '\x02', // access_criteria parameter_type - CONTENT_ID - '\x00', '\x09', // parameter_length - 'C', 'a', 's', 'T', 's', 'F', 'a', 'k', - 'e', // parameter_value - CasTsFake - '\x80', '\x03', // access_criteria parameter_type - CONTENT_PROVIDER - '\x00', '\x0d', // parameter_length - 'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e', - '_', 't', 'e', 's', 't', // parameter_value - widevine_test + 'S', 'D', // parameter_value '\x80', '\x06', // access_criteria parameter_type - CONTENT_IV '\x00', '\x10', // parameter_length - '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', - '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', // + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', // '\x80', '\x06', // access_criteria parameter_type - CONTENT_IV '\x00', '\x10', // parameter_length - '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', - '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', // + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', // + '\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION + '\x00', '\x30', // parameter_length + // parameter_value - ENTITLEMENT_ID (16 bytes) + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + // parameter_value (continued) - ENTITLEMENT_KEY (32 bytes) + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37', // + '\x80', '\x07', // parameter_type - ENTITLEMENT_ID_KEY_COMBINATION + '\x00', '\x30', // parameter_length + // parameter_value - ENTITLEMENT_ID (16 bytes) + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + // parameter_value (continued) - ENTITLEMENT_KEY (32 bytes) + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', // + '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68'}; constexpr char kTestEcmgCwProvisionSingleKey[] = { '\x03', // protocol_version @@ -303,25 +353,25 @@ constexpr char kTestEcmgEcmResponse[] = { '\x00', '\xbc', // parameter_length // parameter_value - ECM_datagram '\x47', '\x40', '\x00', '\x10', '\x00', '\x80', '\x70', '\xa5', '\x4a', - '\xd4', '\x02', '\x0b', '\xc0', '\x66', '\x61', '\x6b', '\x65', '\x5f', - '\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x31', '\x2e', '\x2e', - '\x2e', '\x2e', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd', + '\xd4', '\x02', '\x0b', '\xc0', '\x30', '\x31', '\x32', '\x33', '\x34', + '\x35', '\x36', '\x37', '\x30', '\x31', '\x32', '\x33', '\x34', '\x35', + '\x36', '\x37', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd', '\x34', '\x73', '\xfe', '\x5d', '\x1c', '\x65', '\xa0', '\xbf', '\x93', - '\xfe', '\x01', '\x4b', '\x1d', '\xcd', '\x9e', '\x1d', '\x3a', '\x36', - '\x99', '\x8f', '\x47', '\xa1', '\x3b', '\x46', '\xf1', '\xde', '\x9e', - '\xc2', '\x88', '\xf8', '\x27', '\x2f', '\xea', '\xa1', '\x63', '\x9b', - '\x1b', '\x6a', '\x56', '\x2d', '\x26', '\x31', '\x32', '\x33', '\x34', - '\x35', '\x36', '\x37', '\x38', '\x66', '\x61', '\x6b', '\x65', '\x5f', - '\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x32', '\x2e', '\x2e', - '\x2e', '\x2e', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14', - '\x4d', '\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34', - '\xb4', '\xfd', '\xbe', '\x86', '\x21', '\x35', '\x1e', '\xda', '\x81', - '\x89', '\x6f', '\x70', '\xd3', '\xd2', '\xb2', '\x79', '\xf2', '\xcd', - '\xeb', '\xc5', '\xaf', '\x89', '\xab', '\xeb', '\xf0', '\x1b', '\xd0', - '\xd3', '\xe9', '\x7d', '\x81', '\x8a', '\x31', '\x32', '\x33', '\x34', - '\x35', '\x36', '\x37', '\x38', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', - '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', + '\x90', '\x04', '\x1a', '\xab', '\x1a', '\x46', '\x00', '\xdb', '\xdb', + '\x2f', '\xcb', '\xa5', '\x10', '\x2a', '\x3b', '\x73', '\xf1', '\xfe', + '\x9d', '\x28', '\x2a', '\x3c', '\x0a', '\x5c', '\x58', '\xbc', '\x97', + '\x1d', '\x81', '\x5b', '\x5b', '\xf7', '\x00', '\x01', '\x02', '\x03', + '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0a', '\x0b', '\x0c', + '\x0d', '\x0e', '\x0f', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', + '\x67', '\x68', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', + '\x68', '\xf4', '\x71', '\x2a', '\x4b', '\x6d', '\x6d', '\x14', '\x4d', + '\x2e', '\x53', '\xe7', '\x4b', '\x9f', '\x4b', '\x0a', '\x34', '\x84', + '\xe7', '\xe8', '\xf5', '\x02', '\x39', '\x1f', '\x62', '\x43', '\xb5', + '\xca', '\xb0', '\xee', '\x77', '\xaf', '\xe9', '\x84', '\x41', '\x0a', + '\x16', '\x45', '\x23', '\x6c', '\x1d', '\xe7', '\xdb', '\xfd', '\x91', + '\x91', '\xac', '\x47', '\xbc', '\x10', '\x11', '\x12', '\x13', '\x14', + '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', + '\x1e', '\x1f', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'}; constexpr char kTestEcmgStreamClose[] = { diff --git a/example/wv_cas_ecm_example b/example/wv_cas_ecm_example index 1507c92..54f248c 100644 Binary files a/example/wv_cas_ecm_example and b/example/wv_cas_ecm_example differ diff --git a/example/wv_cas_ecm_example.cc b/example/wv_cas_ecm_example.cc index 9f06fae..c59e00e 100644 --- a/example/wv_cas_ecm_example.cc +++ b/example/wv_cas_ecm_example.cc @@ -17,63 +17,125 @@ #include #include +#include "common/status.h" #include "media_cas_packager_sdk/public/wv_cas_ecm.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" -const size_t kContentIvSize = 8; -const bool kKeyRotation = false; // whether key rotation is enabled -const char kCryptoMode[] = "CTR"; // CBC, CTR, or CSA2 -const int kEcmPid = 149; // PID for the ECM packet +const size_t kContentIvSize = 16; // 8 or 16 +const bool kKeyRotation = true; // whether key rotation is enabled +const char kCryptoMode[] = + "AesScte"; // "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte" +const int kEcmPid = 149; // PID for the ECM packet +const int kAgeRestriction = 0; // Age restriction for the ECM const char kOutputFile[] = - "/tmp/ecm.ts"; // ECM TS packet will be output to here + "/tmp/ecm.ts"; // ECM TS packet will be output to here +const uint8_t kTableId = 0x80; // 0x80 or 0x81 +const char kDefaultTrackTypeSd[] = "SD"; -const char kCsaEvenKey[] = "even_key"; // 8 bytes +const char kEvenKey[] = "even_key........"; // 16 bytes +const char kEvenKeyId[] = "even_key_id....."; // 16 bytes const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes +const char kEvenContentIv16Bytes[] = "even_iv.even_iv."; // 16 bytes const char kEvenEntitlementKeyId[] = "fake_key_id1...."; // 16 bytes const char kEvenEntitlementKey[] = - "fakefakefakefakefakefakefake1..."; // 32 bytes -const char kCsaOddKey[] = "odd_key."; // 8 bytes + "fakefakefakefakefakefakefake1..."; // 32 bytes +const char kEvenWrapIv[] = "even_warp_iv...."; // 16 bytes + +const char kOddKey[] = "odd_key........."; // 16 bytes +const char kOddKeyId[] = "odd_key_id......"; // 16 bytes const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes +const char kOddContentIv16Bytes[] = "odd_iv..odd_iv.."; // 16 bytes const char kOddEntitlementKeyId[] = "fake_key_id2...."; // 16 bytes const char kOddEntitlementKey[] = - "fakefakefakefakefakefakefake2..."; // 32 bytes -const uint8_t kTableId = 0x80; + "fakefakefakefakefakefakefake2..."; // 32 bytes +const char kOddWrapIv[] = "odd_warp_iv....."; // 16 bytes + const size_t kTsPacketSize = 188; -widevine::cas::CryptoMode GetCryptoMode(const std::string& crypto_mode) { - if (crypto_mode.compare("CBC") == 0) { - return widevine::cas::CryptoMode::kAesCbc; +using widevine::cas::EntitlementKeyInfo; +using widevine::cas::WvCasContentKeyInfo; +using widevine::cas::WvCasEcmParameters; + +WvCasEcmParameters CreateWvCasEcmParameters(bool key_rotation, + int content_iv_size) { + WvCasEcmParameters params; + params.content_iv_size = content_iv_size == 8 + ? widevine::cas::kIvSize8 + : widevine::cas::kIvSize16; + params.key_rotation_enabled = key_rotation; + if (!widevine::cas::StringToCryptoMode(kCryptoMode, + ¶ms.crypto_mode)) { + std::cerr << "Unsupported crypto mode " << kCryptoMode << std::endl; } - if (crypto_mode.compare("CTR") == 0) { - return widevine::cas::CryptoMode::kAesCtr; + params.age_restriction = kAgeRestriction; + return params; +} + +std::vector CreateInjectedEntitlements(bool key_rotation) { + std::vector injected_entitlements; + injected_entitlements.reserve(key_rotation ? 2 : 1); + injected_entitlements.emplace_back(); + EntitlementKeyInfo* entitlement = &injected_entitlements.back(); + entitlement->key_id = kEvenEntitlementKeyId; + entitlement->key_value = kEvenEntitlementKey; + entitlement->is_even_key = true; + entitlement->track_type = kDefaultTrackTypeSd; + if (key_rotation) { + injected_entitlements.emplace_back(); + EntitlementKeyInfo* entitlement = &injected_entitlements.back(); + entitlement->key_id = kOddEntitlementKeyId; + entitlement->key_value = kOddEntitlementKey; + entitlement->is_even_key = false; + entitlement->track_type = kDefaultTrackTypeSd; } - return widevine::cas::CryptoMode::kDvbCsa2; + return injected_entitlements; +} + +std::vector CreateContentKeyInfo(bool key_rotation, + int content_iv_size) { + std::vector content_keys; + content_keys.reserve(key_rotation ? 2 : 1); + content_keys.emplace_back(); + WvCasContentKeyInfo* content_key = &content_keys.back(); + content_key->key = kEvenKey; + content_key->key_id = kEvenKeyId; + content_key->content_iv = + content_iv_size == 8 ? kEvenContentIv8Bytes : kEvenContentIv16Bytes; + content_key->wrapped_key_iv = kEvenWrapIv; + if (key_rotation) { + content_keys.emplace_back(); + WvCasContentKeyInfo* content_key = &content_keys.back(); + content_key->key = kOddKey; + content_key->key_id = kOddKeyId; + content_key->content_iv = + content_iv_size == 8 ? kOddContentIv8Bytes : kOddContentIv16Bytes; + content_key->wrapped_key_iv = kOddWrapIv; + } + + return content_keys; } int main(int argc, char** argv) { - // Generate ECM. - widevine::cas::WvCasEcm wv_cas_ecm; - widevine::cas::WvCasStatus status = wv_cas_ecm.Initialize( - kContentIvSize, kKeyRotation, GetCryptoMode(kCryptoMode)); - if (status != widevine::cas::OK) { - std::cerr << "Failed to initialize WV CAS ECM, error: " - << widevine::cas::GetWvCasStatusMessage(status) - << std::endl; - } + WvCasEcmParameters params = + CreateWvCasEcmParameters(kKeyRotation, kContentIvSize); + std::vector entitlements = + CreateInjectedEntitlements(kKeyRotation); + widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements); + + std::vector content_keys = + CreateContentKeyInfo(kKeyRotation, kContentIvSize); std::string ecm; + widevine::Status status; if (kKeyRotation) { - status = wv_cas_ecm.GenerateEcm( - kCsaEvenKey, kEvenContentIv8Bytes, kEvenEntitlementKeyId, - kEvenEntitlementKey, kCsaOddKey, kOddContentIv8Bytes, - kOddEntitlementKeyId, kOddEntitlementKey, &ecm); + status = wv_cas_ecm.GenerateEcm(content_keys[0], content_keys[1], + kDefaultTrackTypeSd, &ecm); } else { - status = wv_cas_ecm.GenerateSingleKeyEcm(kCsaEvenKey, kEvenContentIv8Bytes, - kEvenEntitlementKeyId, - kEvenEntitlementKey, &ecm); + status = wv_cas_ecm.GenerateSingleKeyEcm(content_keys[0], + kDefaultTrackTypeSd, &ecm); } - if (status != widevine::cas::OK) { - std::cerr << "Failed to generate WV CAS ECM, error: " - << widevine::cas::GetWvCasStatusMessage(status) + + if (!status.ok()) { + std::cerr << "Failed to generate WV CAS ECM, error: " << status << std::endl; return -1; } else { @@ -89,8 +151,8 @@ int main(int argc, char** argv) { uint8_t continuity_counter; // not used. status = wv_cas_ecm.GenerateTsPacket(ecm, kEcmPid, kTableId, &continuity_counter, packet); - if (status != widevine::cas::OK) { - std::cerr << "Failed to create ECM TS packet" << std::endl; + if (!status.ok()) { + std::cerr << "Failed to create ECM TS packet: " << status << std::endl; return -1; } else { std::cout << "TS packet bytes: "; diff --git a/example/wv_cas_key_fetcher_example b/example/wv_cas_key_fetcher_example index 65c4df7..5337654 100644 Binary files a/example/wv_cas_key_fetcher_example and b/example/wv_cas_key_fetcher_example differ diff --git a/example/wv_cas_key_fetcher_example.cc b/example/wv_cas_key_fetcher_example.cc index f79589c..7f1e84a 100644 --- a/example/wv_cas_key_fetcher_example.cc +++ b/example/wv_cas_key_fetcher_example.cc @@ -7,65 +7,86 @@ //////////////////////////////////////////////////////////////////////////////// +#include #include +#include -#include "glog/logging.h" -#include "absl/flags/flag.h" -#include "absl/flags/parse.h" #include "common/status.h" #include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" -#include "protos/public/media_cas_encryption.pb.h" -ABSL_FLAG(std::string, content_id, "21140844", "Content ID"); -ABSL_FLAG(bool, key_rotation, true, "Whether key rotation is enabled"); -ABSL_FLAG(std::string, track_type, "SD", "Provider name"); +const char kContentId[] = "21140844"; +const char kContentProvider[] = "widevine"; +const char kTrackType[] = "SD"; +const bool kKeyRotation = false; +const char kSigningProvider[] = "widevine_test"; +const char kSingingKey[] = + "1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9"; +const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249"; +const char kHttpResponse[] = + "{\"response\":" + "\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu" + "dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1" + "JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi" + "OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}"; -int main(int argc, char **argv) { - absl::ParseCommandLine(argc, argv); - CHECK(!absl::GetFlag(FLAGS_content_id).empty() && - !absl::GetFlag(FLAGS_track_type).empty()) - << "Flags 'content_id' and 'track_type' are required"; - // Required flags in key fetcher. - CHECK(!absl::GetFlag(FLAGS_license_server).empty() && - !absl::GetFlag(FLAGS_signing_provider).empty() && - !absl::GetFlag(FLAGS_signing_key).empty() && - !absl::GetFlag(FLAGS_signing_iv).empty()) - << "Flags 'license_server', 'signing_provider', 'signing_key' " - "and 'signing_iv' are required"; +using widevine::Status; +using widevine::cas::EntitlementKeyInfo; +using widevine::cas::EntitlementRequestParams; +using widevine::cas::WvCasKeyFetcher; +class ExampleKeyFetcher : public WvCasKeyFetcher { + public: + ExampleKeyFetcher(const std::string& signing_provider, + const std::string& signing_key, + const std::string& signing_iv) + : WvCasKeyFetcher(signing_provider, signing_key, signing_iv) {} + + // An example that always returns the same response. + Status MakeHttpRequest(const std::string& signed_request_json, + std::string* http_response_json) const override { + *http_response_json = kHttpResponse; + return widevine::OkStatus(); + } +}; + +int main(int argc, char** argv) { + // Initialize key fetcher. + ExampleKeyFetcher key_fetcher(kSigningProvider, kSingingKey, kSingingIv); + + // Create request string. std::string request_str; - widevine::CasEncryptionRequest request; - request.set_provider(absl::GetFlag(FLAGS_signing_provider)); - request.set_content_id(absl::GetFlag(FLAGS_content_id)); - request.set_key_rotation(absl::GetFlag(FLAGS_key_rotation)); - // Only 1 track in this example. - request.add_track_types(absl::GetFlag(FLAGS_track_type)); - LOG(INFO) << "Request: " << request.ShortDebugString(); - if (!request.SerializeToString(&request_str)) { - LOG(ERROR) << "Failed to serialize request"; - return -1; - } - - std::string signed_response_str; - widevine::cas::WvCasKeyFetcher key_fetcher; - widevine::Status status = - key_fetcher.RequestEntitlementKey(request_str, &signed_response_str); + EntitlementRequestParams request_params; + request_params.content_id = kContentId; + request_params.content_provider = kContentProvider; + request_params.track_types = {kTrackType}; + request_params.key_rotation = kKeyRotation; + Status status = + key_fetcher.CreateEntitlementRequest(request_params, &request_str); if (!status.ok()) { - LOG(ERROR) << "Failed to request entitlement key"; - return -1; + std::cerr << "Failed to create entitlement request, error: " << status + << std::endl; } - widevine::SignedCasEncryptionResponse signed_response; - if (!signed_response.ParseFromString(signed_response_str)) { - LOG(ERROR) << "Failed to deserialize signed response"; - return -1; + std::cout << "Request: " << request_str << std::endl; + + // Request entitlement keys. + std::string signed_response_str; + status = key_fetcher.MakeHttpRequest(request_str, &signed_response_str); + if (!status.ok()) { + std::cerr << "Failed to request entitlement key, error: " << status + << std::endl; } - LOG(INFO) << "Signed response: " << signed_response.ShortDebugString(); - widevine::CasEncryptionResponse response; - if (!response.ParseFromString(signed_response.response())) { - LOG(ERROR) << "Failed to deserialize response"; - return -1; + std::cout << "Response: " << signed_response_str << std::endl; + + // Parse entitlement key response. + std::vector entitlements; + status = + key_fetcher.ParseEntitlementResponse(signed_response_str, &entitlements); + if (!status.ok()) { + std::cerr << "Failed to parse entitlement response, error: " << status + << std::endl; } - LOG(INFO) << "Response: " << response.ShortDebugString(); + std::cout << "Parsed: " << entitlements.size() << " entitlement keys." + << std::endl; return 0; } diff --git a/example/wv_cas_types_example b/example/wv_cas_types_example index 5bcaab7..528ff1a 100644 Binary files a/example/wv_cas_types_example and b/example/wv_cas_types_example differ diff --git a/libmedia_cas_packager_sdk.so b/libmedia_cas_packager_sdk.so index e081ede..eed33f9 100755 Binary files a/libmedia_cas_packager_sdk.so and b/libmedia_cas_packager_sdk.so differ diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h index 7323d56..1a044fc 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h @@ -14,7 +14,7 @@ #include #include -#include "media_cas_packager_sdk/public/wv_cas_types.h" +#include "common/status.h" namespace widevine { namespace cas { @@ -55,9 +55,10 @@ class WvCasCaDescriptor { // section (for an EMM stream) or into a TS Program Map Table section (for an // ECM stream). The descriptor will be 6 bytes plus any bytes added as // (user-defined) private data. - virtual WvCasStatus GenerateCaDescriptor( - uint16_t ca_pid, const std::string& provider, const std::string& content_id, - std::string* serialized_ca_desc) const; + virtual Status GenerateCaDescriptor(uint16_t ca_pid, + const std::string& provider, + const std::string& content_id, + std::string* serialized_ca_desc) const; // Return the base size (before private data is added) of the CA // descriptor. The user can call this to plan the layout of the Table section diff --git a/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc new file mode 100644 index 0000000..432a9cb --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.cc @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 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_curl_key_fetcher.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "curl/curl.h" +#include "curl/easy.h" + +namespace widevine { +namespace cas { + +size_t AppendToString(void* ptr, size_t size, size_t count, + std::string* output) { + const absl::string_view data(static_cast(ptr), size * count); + absl::StrAppend(output, data); + return data.size(); +} + +Status WvCasCurlKeyFetcher::MakeHttpRequest( + const std::string& signed_request_json, + std::string* http_response_json) const { + if (license_server_.empty()) { + return Status(error::NOT_FOUND, "license_server is empty"); + } + if (http_response_json == nullptr) { + return Status(error::INVALID_ARGUMENT, "http_response_json is null"); + } + + CURL* curl; + CURLcode curl_code; + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, license_server_.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString); + // If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself. + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, + (int64_t)strlen(signed_request_json.c_str())); + curl_code = curl_easy_perform(curl); + if (curl_code != CURLE_OK) { + return Status(error::INTERNAL, + "curl_easy_perform() failed: " + + std::string(curl_easy_strerror(curl_code))); + } + curl_easy_cleanup(curl); + } else { + return Status(error::INTERNAL, "curl_easy_init() failed"); + } + return OkStatus(); +} + +} // namespace cas +} // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h new file mode 100644 index 0000000..9d98c90 --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_curl_key_fetcher.h @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 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_CURL_KEY_FETCHER_H_ +#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_ + +#include + +#include "common/status.h" +#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" + +namespace widevine { +namespace cas { + +class WvCasCurlKeyFetcher : public WvCasKeyFetcher { + public: + WvCasCurlKeyFetcher(const std::string& license_server, + const std::string& signing_provider, + const std::string& signing_key, + const std::string& signing_iv) + : WvCasKeyFetcher(signing_provider, signing_key, signing_iv), + license_server_(license_server) {} + + Status MakeHttpRequest(const std::string& signed_request_json, + std::string* http_response_json) const override; + + private: + std::string license_server_; +}; + +} // namespace cas +} // namespace widevine + +#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_CURL_KEY_FETCHER_H_ diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index fafc608..fe59137 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -10,102 +10,91 @@ #define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_ #include +#include #include +#include "common/status.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" namespace widevine { namespace cas { +// Information needed to generate content key portion of ECM. +// Fields: +// |key_id| key ID for the content key, must be 16 bytes. +// |key| content key value (aka control word), must be 16 bytes. It will be +// wrapped (encrypted) by corresponding entitlement key (decided by track +// type), together with |wrapped_key_iv| as the initial vector. +// |content_iv| content initial vector, must be 8 or 16 bytes. It is used for +// decrypting the content stream. +// |wrapped_key_iv| must be 16 bytes. It is used to encrypt |key|. +struct WvCasContentKeyInfo { + std::string key_id; + std::string key; + std::string content_iv; + std::string wrapped_key_iv; +}; + +// 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 EcmIvSize 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 CryptoMode. +// |age_restriction| minimum age required; the value represents actual age. +struct WvCasEcmParameters { + EcmIvSize content_iv_size = kIvSize8; + bool key_rotation_enabled = true; + CryptoMode crypto_mode = CryptoMode::kAesCtr; + uint8_t age_restriction = 0; +}; + // 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; + // Construct of class WvCasEcm. + // Args: + // |ecm_init_parameters| encryption-related parameters for configuring + // the ECM stream. + // |injected_entitlement| entitlement key info. May be fetched from the server + // with WvCasKeyFetcher. + WvCasEcm(const WvCasEcmParameters& ecm_parameters, + const std::vector& injected_entitlements); WvCasEcm(const WvCasEcm&) = delete; WvCasEcm& operator=(const WvCasEcm&) = delete; virtual ~WvCasEcm() = default; - // Initialize an instance of this class. - // + // Accept keys and IVs and construct an ECM that will fit into a Transport + // Stream packet payload (184 bytes). // Args: - // - |content_iv_size| iv size in bytes for encrypting the content, - // only support 8 bytes or 16 bytes - // When using 8 bytes content_iv, we assume additional 8 bytes of '\x00' are - // appended to the iv to form a 16 bytes AES IV when content is encrypted. - // - |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 - // - // 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 - virtual WvCasStatus Initialize(int content_iv_size, bool key_rotation_enabled, - CryptoMode crypto_mode); + // |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. + // 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. + virtual Status GenerateEcm(const WvCasContentKeyInfo& even_key, + const WvCasContentKeyInfo& odd_key, + const std::string& track_type, + std::string* serialized_ecm) const; - // Generate an ECM containing two keys (even and odd). Can be called when - // |key_rotation_enabled| is initialized to 'true'. - // - // Args (all pointer parameters must be not nullptr): - // - |even_key| clear even content key, must be 8 bytes for kDvbCsa2, - // 16 bytes for kAesCtr or kAesCbc - // - |even_content_iv| iv used along with |even_key| for encrypting content, - // length must match |content_iv_size| set during initialization - // - |even_entitlement_key_id| key id for |even_entitlement_key|, - // must be 16 bytes length - // - |even_entitlement_key| entitlement key used to encrypt even key, - // must be 32 bytes length - // - |odd_key| clear odd content key, must be 8 bytes for kDvbCsa2, - // 16 bytes for kAesCtr or kAesCbc - // - |odd_content_iv| iv used along with |odd_key| for encrypting content, - // length must match |content_iv_size| set during initialization - // - |odd_entitlement_key_id| key id for |odd_entitlement_key|, - // must be 16 bytes length - // - |odd_entitlement_key| entitlement key used to encrypt odd key, - // must be 32 bytes length - // - |ecm| for returning the generated ECM, - // size of the generated ecm is 149 bytes when content iv is 8 bytes - // 165 bytes when content iv is 16 bytes - // - // Returns: - // - A status indicating whether there was any error during processing - virtual WvCasStatus GenerateEcm(const char* const even_key, - const char* const even_content_iv, - const char* const even_entitlement_key_id, - const char* const even_entitlement_key, - const char* const odd_key, - const char* const odd_content_iv, - const char* const odd_entitlement_key_id, - const char* const odd_entitlement_key, - std::string* ecm) const; - - // Generate an ECM containing only a singe even key. Can be called when - // |key_rotation_enabled| is initialized to 'false'. - // - // Args (all pointer parameters must be not nullptr): - // - |even_key| clear even content key, must be 8 bytes for kDvbCsa2, - // 16 bytes for kAesCtr or kAesCbc - // - |even_content_iv| iv used along with |even_key| for encrypting content, - // length must match |content_iv_size| set during initialization - // - |even_entitlement_key_id| key id for |even_entitlement_key|, - // must be 16 bytes length - // - |even_entitlement_key| entitlement key used to encrypt even key, - // must be 32 bytes length - // - |ecm| for returning the generated ECM, - // size of the generated ecm is 77 bytes when content iv is 8 bytes - // 85 bytes when content iv is 16 bytes - // - // Returns: - // - A status indicating whether there was any error during processing - virtual WvCasStatus GenerateSingleKeyEcm( - const char* const even_key, const char* const even_content_iv, - const char* const even_entitlement_key_id, - const char* const even_entitlement_key, std::string* ecm) const; + // 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. + // The |key| contents (specifically IV sizes) must be consistent + // with the initialized settings. + virtual Status GenerateSingleKeyEcm(const WvCasContentKeyInfo& key, + const std::string& track_type, + std::string* serialized_ecm) const; // Generate a TS packet with the given |ecm| as payload. // @@ -125,16 +114,13 @@ class WvCasEcm { // // Returns: // - A status indicating whether there was any error during processing - virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid, - uint8_t table_id, - uint8_t* continuity_counter, - uint8_t* packet) const; + static Status GenerateTsPacket(const std::string& ecm, uint16_t pid, + uint8_t table_id, uint8_t* continuity_counter, + uint8_t* packet); private: - bool initialized_ = false; - int content_iv_size_; - bool key_rotation_enabled_; - CryptoMode crypto_mode_; + WvCasEcmParameters ecm_param_; + std::vector injected_entitlements_; }; } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc deleted file mode 100644 index 6b9e7fb..0000000 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc +++ /dev/null @@ -1,160 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// 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_key_fetcher.h" - -#include -#include - -#include -#include "glog/logging.h" -#include "google/protobuf/util/json_util.h" -#include "absl/flags/flag.h" -#include "absl/strings/escaping.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "curl/curl.h" -#include "curl/easy.h" -#include "common/signature_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; - -ABSL_FLAG(std::string, license_server, "", - "HTTP URL to the license server for making CAS encryption request."); -ABSL_FLAG(std::string, signing_provider, "", - "Name of the provider signing the CAS encryption request."); -ABSL_FLAG(std::string, signing_key, "", - "AES key (in hex) for signing the CAS encryption request."); -ABSL_FLAG(std::string, signing_iv, "", - "AES iv (in hex) for signing the CAS encryption request."); - -namespace widevine { -namespace cas { - -Status WvCasKeyFetcher::RequestEntitlementKey( - const std::string& request_string, - std::string* signed_response_string) const { - if (absl::GetFlag(FLAGS_signing_provider).empty() || - absl::GetFlag(FLAGS_signing_key).empty() || - absl::GetFlag(FLAGS_signing_iv).empty()) { - return Status( - error::INVALID_ARGUMENT, - "Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty"); - } - - // Processes request. - CasEncryptionRequest request; - request.ParseFromString(request_string); - std::string request_json; - 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, &request_json, print_options).ok()) { - return Status(error::INTERNAL, - "Failed to convert request message to json."); - } - LOG(INFO) << "Json CasEncryptionRequest: " << request_json; - - // Creates signed request. - SignedCasEncryptionRequest signed_request; - signed_request.set_request(request_json); - std::string signature; - if (!signature_util::GenerateAesSignature( - request_json, - absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_key)), - absl::HexStringToBytes(absl::GetFlag(FLAGS_signing_iv)), &signature) - .ok()) { - return Status(error::INTERNAL, "Failed to sign the request."); - } - signed_request.set_signature(signature); - signed_request.set_signer(absl::GetFlag(FLAGS_signing_provider)); - std::string signed_request_json; - // NOTE: MessageToJsonString will automatically converts the 'request' and - // 'signature' fields in SignedCasEncryptionRequest to base64, because they - // are of type 'bytes'. - if (!MessageToJsonString(signed_request, &signed_request_json).ok()) { - return Status(error::INTERNAL, - "Failed to convert signed request message to json."); - } - LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json; - - // Makes HTTP request against License Server. - std::string http_response_json; - Status status = MakeHttpRequest(signed_request_json, &http_response_json); - if (!status.ok()) { - return status; - } - LOG(INFO) << "Json HTTP response: " << http_response_json; - HttpResponse http_response; - if (!JsonStringToMessage(http_response_json, &http_response).ok()) { - return Status(error::INTERNAL, - "Failed to convert http response json to message."); - } - - // Processes signed response. - LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response(); - CasEncryptionResponse response; - if (!JsonStringToMessage(http_response.response(), &response).ok()) { - return Status(error::INTERNAL, - "Failed to convert response json to message."); - } - SignedCasEncryptionResponse signed_response; - signed_response.set_response(response.SerializeAsString()); - signed_response.SerializeToString(signed_response_string); - return OkStatus(); -} - -size_t AppendToString(void* ptr, size_t size, size_t count, - std::string* output) { - const absl::string_view data(static_cast(ptr), size * count); - absl::StrAppend(output, data); - return data.size(); -} - -Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json, - std::string* http_response_json) const { - CHECK(http_response_json); - if (absl::GetFlag(FLAGS_license_server).empty()) { - return Status(error::INVALID_ARGUMENT, "Flag 'license_server' is empty"); - } - CURL* curl; - CURLcode curl_code; - curl = curl_easy_init(); - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, - absl::GetFlag(FLAGS_license_server).c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString); - // If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself. - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, - (int64_t)strlen(signed_request_json.c_str())); - curl_code = curl_easy_perform(curl); - if (curl_code != CURLE_OK) { - return Status(error::INTERNAL, - "curl_easy_perform() failed: " + - std::string(curl_easy_strerror(curl_code))); - } - curl_easy_cleanup(curl); - } else { - return Status(error::INTERNAL, "curl_easy_init() failed"); - } - return OkStatus(); -} - -} // namespace cas -} // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h index 815c93d..35c9ac4 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h @@ -11,50 +11,86 @@ #include -#include "absl/flags/declare.h" #include "common/status.h" -#include "media_cas_packager_sdk/internal/key_fetcher.h" +#include "media_cas_packager_sdk/public/wv_cas_types.h" namespace widevine { namespace cas { +// Paramerters that are needed to construct an entitlement key rquest message. +// Fields: +// |content_id| uniquely identifies the content (with |content_provider|) +// |content_provider| unique std::string for provider of the content stream. +// |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. +// |key_rotation| the encryption uses two keys if set. Two entitlement keys +// are requested for each track type. +struct EntitlementRequestParams { + std::string content_id; + std::string content_provider; + std::vector track_types; + bool key_rotation; +}; + // WV CAS KeyFetcher. Performs the communication with the Widevine License // Server to get entitlement keys on behalf of a WvCasEcm object. -class WvCasKeyFetcher : public KeyFetcher { +class WvCasKeyFetcher { public: - WvCasKeyFetcher() = default; + // Initialize an WvCasKeyFetcher object with required infomartion. + // Args: + // |signing_provider| name of the provider signing the CAS encryption + // request. + // |signing_key| AES key (in hex) for signing the CAS encryption request. + // |signing_iv| AES iv (in hex) for signing the CAS encryption request. + WvCasKeyFetcher(const std::string& signing_provider, + const std::string& signing_key, + const std::string& signing_iv); WvCasKeyFetcher(const WvCasKeyFetcher&) = delete; WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete; - ~WvCasKeyFetcher() override = default; + virtual ~WvCasKeyFetcher() = default; - // Get entitlement keys from the license server. Send a - // SignedCasEncryptionRequest message to the license server, receive a - // SignedCasEncryptionResponse and return it to the caller. + // Create a SignedCasEncryptionRequest message that can be used to request + // entitlement keys from a server. // Args: - // |request_string| a serialized CasEncryptionRequest message, produced - // by WvCasEcm::Initialize(). - // |signed_response_string| a serialized SignedCasEncryptionResponse - // message. It should be passed into - // widevine::cas::Ecm::ProcessCasEncryptionResponse(). - Status RequestEntitlementKey( - const std::string& request_string, - std::string* signed_response_string) const override; + // |request_params| paramerters that are needed to request entitlement keys + // from the server. + // |signed_request_json| the created serialized SignedCasEncryptionRequest + // message. + virtual Status CreateEntitlementRequest( + const EntitlementRequestParams& request_params, + std::string* signed_request_json) const; + + // 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_string| a serialized CasEncryptionRequest message from the server + // holding entitlement key information (or error information). + // |entitlements| a pointer holding the resulting entitlement keys parsed from + // the response string. + static Status ParseEntitlementResponse( + const std::string& http_response_json, + std::vector* entitlements); - protected: // Makes a HTTP request to License Server for entitlement key(s). // Returns the HTTP response in Json format in |http_response_json|. - // Protected visibility to support unit testing. virtual Status MakeHttpRequest(const std::string& signed_request_json, - std::string* http_response_json) const; + std::string* http_response_json) const = 0; + + // Conventient function that calls CreateEntitlementRequest, + // MakeHttpRequest, and ParseEntitlementResponse in sequence. + virtual Status FetchEntitlements( + const EntitlementRequestParams& request_params, + std::vector* entitlements) const; + + private: + std::string signing_provider_; + std::string signing_key_; + std::string signing_iv_; }; } // namespace cas } // namespace widevine -// Exposed for testing and example. -ABSL_DECLARE_FLAG(std::string, license_server); -ABSL_DECLARE_FLAG(std::string, signing_provider); -ABSL_DECLARE_FLAG(std::string, signing_key); -ABSL_DECLARE_FLAG(std::string, signing_iv); - #endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_ diff --git a/media_cas_packager_sdk/public/wv_cas_types.cc b/media_cas_packager_sdk/public/wv_cas_types.cc index 95694e1..afe0f84 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.cc +++ b/media_cas_packager_sdk/public/wv_cas_types.cc @@ -20,30 +20,6 @@ using google::protobuf::util::MessageToJsonString; namespace widevine { namespace cas { -static const char* kWvCasStatusMessage[] = { - "OK", // OK = 0, - "", - "", - "Invalid argument", // INVALID_ARGUMENT = 3, - "", - "Not found", // NOT_FOUND = 5, - "Already exists", // ALREADY_EXISTS = 6, - "Permission denied", // PERMISSION_DENIED = 7, - "", - "", - "", - "", - "Unimplemented", // UNIMPLEMENTED = 12, - "Internal", // INTERNAL = 13, - "Unavailable", // UNAVAILABLE = 14, -}; - -std::string GetWvCasStatusMessage(WvCasStatus status) { - static_assert(arraysize(kWvCasStatusMessage) == NUM_WV_CAS_STATUS, - "mismatching status message and status."); - return kWvCasStatusMessage[status]; -} - // Numeric value of crypto mode is the index into strings array. static const char* kCrypoModeStrings[] = { "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte", @@ -109,8 +85,8 @@ bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode) { return false; } -WvCasStatus CreateWvCasEncryptionRequestJson( - const WvCasEncryptionRequest& request, std::string* request_json) { +Status CreateWvCasEncryptionRequestJson(const WvCasEncryptionRequest& request, + std::string* request_json) { CHECK(request_json); CasEncryptionRequest request_proto; @@ -131,14 +107,14 @@ WvCasStatus CreateWvCasEncryptionRequestJson( // to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='. if (!MessageToJsonString(request_proto, request_json, print_options).ok()) { LOG(ERROR) << "Failed to convert request message to json."; - return INTERNAL; + return Status(error::INTERNAL); } - return OK; + return OkStatus(); } -WvCasStatus ParseWvCasEncryptionResponseJson( - const std::string& response_json, WvCasEncryptionResponse* response) { +Status ParseWvCasEncryptionResponseJson(const std::string& response_json, + WvCasEncryptionResponse* response) { CHECK(response); CasEncryptionResponse response_proto; @@ -146,7 +122,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson( // 'bytes' type fields. if (!JsonStringToMessage(response_json, &response_proto).ok()) { LOG(ERROR) << "Failed to convert response json to message."; - return INTERNAL; + return Status(error::INTERNAL); } response->status = @@ -163,7 +139,7 @@ WvCasStatus ParseWvCasEncryptionResponseJson( response->entitlement_keys.push_back(key_info); } - return OK; + return OkStatus(); } } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index cbea067..e5c882c 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -12,45 +12,11 @@ #include #include +#include "common/status.h" + namespace widevine { namespace cas { -enum WvCasStatus { - // Success. - OK = 0, - - // Client specified an invalid argument. - INVALID_ARGUMENT = 3, - - // Some requested entity (e.g., file or directory) was not found. - NOT_FOUND = 5, - - // Some entity that we attempted to create (e.g., file or directory) - // already exists. - ALREADY_EXISTS = 6, - - // The caller does not have permission to execute the specified - // operation. - PERMISSION_DENIED = 7, - - // Operation is not implemented or not supported/enabled in this service. - UNIMPLEMENTED = 12, - - // Internal errors. Means some invariants expected by underlying - // system has been broken. If you see one of these errors, - // something is very broken. - INTERNAL = 13, - - // Operation is not implemented or not supported/enabled in this service. - UNAVAILABLE = 14, - - // Number of errors. - NUM_WV_CAS_STATUS, -}; - -// Returns the message std::string for the given WvCasStatus. -std::string GetWvCasStatusMessage(WvCasStatus status); - // Crypto mode for encryption / decryption. ENUM value should be consistent with // ECM V2 definition. Largest supported value for this CryptoMode ENUM is 15. enum class CryptoMode : int { @@ -77,6 +43,28 @@ bool ScramblingLevelToString(ScramblingLevel mode, std::string* str); // Returns false if str is not a valid ScramblingLevel. bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode); +// 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 for the injected entitlement keys. Used for Ecm +// initialization. +// Fields: +// |track_type| the track that the key is being used to encrypt. +// |is_even_key| if true, this entitlement is an even key. For the single key +// case (no key rotation), is_even_key is always true. +// |key_id| key ID for this entitlement key, must be 16 bytes. +// |key_value| entitlement key value, must be 32 bytes. Used to decrypt +// content keys. +struct EntitlementKeyInfo { + std::string track_type; + bool is_even_key; + std::string key_id; // must be 16 bytes. + std::string key_value; // must be 32 bytes. +}; + struct WvCasEncryptionRequest { std::string content_id; std::string provider; @@ -124,15 +112,15 @@ struct WvCasEncryptionResponse { // request JSON message. // And that signed JSON message can be sent to Widevine license server for // acquiring entitlement keys. -WvCasStatus CreateWvCasEncryptionRequestJson( - const WvCasEncryptionRequest& request, std::string* request_json); +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. -WvCasStatus ParseWvCasEncryptionResponseJson(const std::string& response_json, - WvCasEncryptionResponse* response); +Status ParseWvCasEncryptionResponseJson(const std::string& response_json, + WvCasEncryptionResponse* response); } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/public/wv_ecmg b/media_cas_packager_sdk/public/wv_ecmg index 9432caf..803d79b 100644 Binary files a/media_cas_packager_sdk/public/wv_ecmg and b/media_cas_packager_sdk/public/wv_ecmg differ diff --git a/protos/public/BUILD b/protos/public/BUILD new file mode 100644 index 0000000..cd40ec4 --- /dev/null +++ b/protos/public/BUILD @@ -0,0 +1,36 @@ +################################################################################ +# 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. +################################################################################ + +# Protocol buffer definitions for Widevine media cas packager sdk. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "binary_release_files", + srcs = glob(["**"]), +) + +proto_library( + name = "media_cas_encryption_proto", + srcs = ["media_cas_encryption.proto"], +) + +cc_proto_library( + name = "media_cas_encryption_cc_proto", + deps = [":media_cas_encryption_proto"], +) + +proto_library( + name = "media_cas_proto", + srcs = ["media_cas.proto"], +) + +cc_proto_library( + name = "media_cas_cc_proto", + deps = [":media_cas_proto"], +) diff --git a/protos/public/media_cas.proto b/protos/public/media_cas.proto new file mode 100644 index 0000000..928c3ce --- /dev/null +++ b/protos/public/media_cas.proto @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// + + +syntax = "proto2"; + +package widevine.cas; + +// Widevine private data in the CA descriptor. +message CaDescriptorPrivateData { + // Provider name. + optional string provider = 1; + + // Content ID. + optional bytes content_id = 2; +} diff --git a/protos/public/media_cas_encryption.proto b/protos/public/media_cas_encryption.proto new file mode 100644 index 0000000..84f8044 --- /dev/null +++ b/protos/public/media_cas_encryption.proto @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +// Protocol buffer definitions for Widevine CAS. + +syntax = "proto2"; + +package widevine; + +option java_package = "com.google.video.widevine.mediacasencryption"; + +message CasEncryptionRequest { + optional bytes content_id = 1; + optional string provider = 2; + // Optional track types such as "AUDIO", SD" or "HD". + repeated string track_types = 3; + // 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. + optional bool key_rotation = 4; +} + +message CasEncryptionResponse { + enum 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; + } + message KeyInfo { + enum KeySlot { + KEY_SLOT_UNSPECIFIED = 0; + SINGLE = 1; + EVEN = 2; + ODD = 3; + } + optional bytes key_id = 1; + optional bytes key = 2; + // Optional label used for the key. + optional string track_type = 3; + optional KeySlot key_slot = 4; + } + optional Status status = 1; + optional string status_message = 2; + optional bytes content_id = 3; + repeated KeyInfo entitlement_keys = 4; +} + +message SignedCasEncryptionRequest { + optional bytes request = 1; + optional bytes signature = 2; + // Identifies the entity sending / signing the request. + optional string signer = 3; +} + +message SignedCasEncryptionResponse { + // Serialized CasEncryptionResponse message. + optional bytes response = 1; + optional bytes signature = 2; +} + +message HttpResponse { + optional bytes response = 1; +}