Decouple key fetcher; Update ECMG API

This commit is contained in:
Widevine Buildbot
2020-02-12 02:14:37 +00:00
parent 05c27554f4
commit 62a2cc3e52
21 changed files with 838 additions and 509 deletions

22
common/certificate_type.h Normal file
View File

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

116
common/status.h Normal file
View File

@@ -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 <iosfwd>
#include <string>
#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_

View File

@@ -29,7 +29,7 @@ constexpr char kTestEcmgChannelSetup[] = {
constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = {
'\x03', // protocol_version
'\x00', '\x01', // message_type - Channel_setup
'\x00', '\x70', // message_length
'\x00', '\x8c', // message_length
'\x00', '\x0e', // parameter_type - ECM_channel_id
'\x00', '\x02', // parameter_length
'\x00', '\x01', // parameter_value
@@ -45,25 +45,26 @@ constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = {
'\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'};
'\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,15 +263,15 @@ 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
@@ -246,22 +284,34 @@ constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = {
'\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
'\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[] = {

Binary file not shown.

View File

@@ -17,63 +17,125 @@
#include <string>
#include <cstdint>
#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 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
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
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;
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,
&params.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;
}
return widevine::cas::CryptoMode::kDvbCsa2;
std::vector<EntitlementKeyInfo> CreateInjectedEntitlements(bool key_rotation) {
std::vector<EntitlementKeyInfo> 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 injected_entitlements;
}
std::vector<WvCasContentKeyInfo> CreateContentKeyInfo(bool key_rotation,
int content_iv_size) {
std::vector<WvCasContentKeyInfo> 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<EntitlementKeyInfo> entitlements =
CreateInjectedEntitlements(kKeyRotation);
widevine::cas::WvCasEcm wv_cas_ecm(params, entitlements);
std::vector<WvCasContentKeyInfo> 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: ";

Binary file not shown.

View File

@@ -7,65 +7,86 @@
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <string>
#include <vector>
#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==\"}";
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) {
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";
// 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<widevine::cas::EntitlementKeyInfo> 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;
}

Binary file not shown.

Binary file not shown.

View File

@@ -14,7 +14,7 @@
#include <string>
#include <cstdint>
#include "media_cas_packager_sdk/public/wv_cas_types.h"
#include "common/status.h"
namespace widevine {
namespace cas {
@@ -55,8 +55,9 @@ 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,
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

View File

@@ -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<char*>(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

View File

@@ -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 <string>
#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_

View File

@@ -10,102 +10,91 @@
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <string>
#include <vector>
#include <cstdint>
#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<EntitlementKeyInfo>& 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<EntitlementKeyInfo> injected_entitlements_;
};
} // namespace cas

View File

@@ -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 <stddef.h>
#include <string.h>
#include <cstdint>
#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<char*>(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

View File

@@ -11,50 +11,86 @@
#include <string>
#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<std::string> 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<EntitlementKeyInfo>* 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<EntitlementKeyInfo>* 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_

View File

@@ -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

View File

@@ -12,45 +12,11 @@
#include <string>
#include <vector>
#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,14 +112,14 @@ 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,
Status ParseWvCasEncryptionResponseJson(const std::string& response_json,
WvCasEncryptionResponse* response);
} // namespace cas

36
protos/public/BUILD Normal file
View File

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

View File

@@ -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;
}

View File

@@ -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;
}