Group license support

This commit is contained in:
Lu Chen
2021-03-05 16:09:59 -08:00
parent 00785b2ccd
commit 5c81c1aa9e
30 changed files with 1236 additions and 585 deletions

View File

@@ -32,6 +32,11 @@ typedef enum {
LICENSE_CAS_RENEWAL_READY,
LICENSE_REMOVAL,
LICENSE_REMOVED,
ASSIGN_LICENSE_ID,
LICENSE_ID_ASSIGNED,
LICENSE_NEW_EXPIRY_TIME,
MULTI_CONTENT_LICENSE_INFO,
GROUP_LICENSE_INFO,
// TODO(jfore): Evaluate removing this event in favor of return status codes
// from
@@ -87,4 +92,16 @@ typedef enum {
SERVICE_BLOCKING_DEVICE_GROUP = 0,
} SessionServiceBlockingFieldType;
// Types used inside a MULTI_CONTENT_LICENSE_INFO event.
typedef enum {
MULTI_CONTENT_LICENSE_ID = 0,
MULTI_CONTENT_LICENSE_CONTENT_ID,
} MultiContentLicenseFieldType;
// Types used inside a GROUP_LICENSE_INFO event.
typedef enum {
GROUP_LICENSE_ID = 0,
GROUP_LICENSE_GROUP_ID,
} GroupLicenseFieldType;
#endif // CAS_EVENTS_H

View File

@@ -53,15 +53,22 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
std::string* signed_license_request);
// Restores a stored license making the keys available for use.
// If |content_id_filter| is not null, only matching entitlement keys (as
// specified in KeyCategory) will be installed.
virtual CasStatus HandleStoredLicense(const std::string& wrapped_rsa_key,
const std::string& license_file);
const std::string& license_file,
const std::string* content_id_filter);
// Process a server response containing a EMM for use in the
// processing of ECM(s). If |device_file| is not nullptr and the license
// policy allows a license to be stored |device_file| is populated with the
// bytes of the license secured for storage.
// Process a server response containing a EMM for use in the processing of
// ECM(s).
// If |content_id_filter| is not null, only matching entitlement keys (as
// specified in KeyCategory) will be installed.
// If |device_file| is not nullptr and the license policy allows a license to
// be stored |device_file| is populated with the bytes of the license secured
// for storage.
virtual CasStatus HandleEntitlementResponse(
const std::string& entitlement_response, std::string* device_file);
const std::string& entitlement_response,
const std::string* content_id_filter, std::string* device_file);
// Process a previously stored device |certificate| and make it available
// for use in an EMM request.
@@ -92,6 +99,22 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
// Query the license to see if storage is allowed.
virtual bool CanStoreLicense() const;
// Returns the group id specified in the license. Group id is expected to be
// non-empty if the license is MULTI_CONTENT_LICENSE or GROUP_LICENSE; and
// empty if the license is SINGLE_CONTENT_LICENSE_DEFAULT.
virtual std::string GetGroupId() const;
// If the license is MULTI_CONTENT_LICENSE, the returned vector contains all
// content ids that the license is for. Returns empty if the license if not
// MULTI_CONTENT_LICENSE.
virtual std::vector<std::string> GetContentIdList() const;
// Returns true if the license is MULTI_CONTENT_LICENSE, and false otherwise.
virtual bool IsMultiContentLicense() const;
// Returns true if the license is GROUP_LICENSE, and false otherwise.
virtual bool IsGroupLicense() const;
// Policy timer implentation.
void OnTimerEvent() override;
@@ -133,7 +156,8 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
CasStatus InstallLicense(const std::string& session_key,
const std::string& serialized_license,
const std::string& core_message,
const std::string& signature);
const std::string& signature,
const std::string* content_id_filter);
CasStatus InstallLicenseRenewal(const std::string& serialized_license,
const std::string& core_message,
const std::string& signature);

View File

@@ -1,50 +0,0 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CRYPTO_KEY_H_
#define CRYPTO_KEY_H_
namespace wvcas {
class CryptoKey {
public:
CryptoKey(){};
~CryptoKey(){};
const std::string& key_id() const { return key_id_; }
const std::string& key_data() const { return key_data_; }
const std::string& key_data_iv() const { return key_data_iv_; }
const std::string& key_control() const { return key_control_; }
const std::string& key_control_iv() const { return key_control_iv_; }
const std::string& entitlement_key_id() const { return entitlement_key_id_; }
const std::string& track_label() const { return track_label_; }
void set_key_id(const std::string& key_id) { key_id_ = key_id; }
void set_key_data(const std::string& key_data) { key_data_ = key_data; }
void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; }
void set_key_control(const std::string& ctl) { key_control_ = ctl; }
void set_key_control_iv(const std::string& ctl_iv) {
key_control_iv_ = ctl_iv;
}
void set_track_label(const std::string& track_label) {
track_label_ = track_label;
}
void set_entitlement_key_id(const std::string& entitlement_key_id) {
entitlement_key_id_ = entitlement_key_id;
}
bool HasKeyControl() const { return key_control_.size() >= 16; }
private:
std::string key_id_;
std::string key_data_iv_;
std::string key_data_;
std::string key_control_;
std::string key_control_iv_;
std::string track_label_;
std::string entitlement_key_id_;
};
} // namespace wvcas
#endif // CRYPTO_KEY_H_

View File

@@ -13,7 +13,6 @@
#include "OEMCryptoCAS.h"
#include "cas_status.h"
#include "cas_types.h"
#include "crypto_key.h"
#include "oemcrypto_interface.h"
#include "rw_lock.h"
@@ -144,8 +143,6 @@ class CryptoInterface {
size_t enc_session_key_length, const uint8_t* mac_key_context,
size_t mac_key_context_length, const uint8_t* enc_key_context,
size_t enc_key_context_length);
virtual OEMCryptoResult OEMCrypto_LoadKeys(
const LoadKeysParams& load_key_params);
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
@@ -168,10 +165,6 @@ class CryptoInterface {
OEMCryptoCipherMode cipher_mode);
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max);
virtual OEMCryptoResult OEMCrypto_RefreshKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array);
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength);
virtual const char* OEMCrypto_SecurityLevel();
@@ -267,13 +260,6 @@ class CryptoSession {
size_t mac_key_context_length,
const uint8_t* enc_key_context,
size_t enc_key_context_length);
virtual CasStatus LoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& key_array,
const std::string& pst,
const std::string& srm_requirement);
virtual CasStatus LoadLicense(const std::string& signed_message,
const std::string& core_message,
const std::string& signature);
@@ -292,9 +278,6 @@ class CryptoSession {
CryptoMode crypto_mode);
virtual bool GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max);
virtual CasStatus RefreshKeys(const std::string& message,
const std::string& signature,
const std::vector<CryptoKey>& key_array);
virtual CasStatus GetDeviceID(std::string* buffer);
virtual const char* SecurityLevel();
virtual CasStatus CreateEntitledKeySession(

View File

@@ -24,7 +24,7 @@ class EcmParser {
// The EcmParser factory method.
// Validates the ecm. If validations is successful returns true and constructs
// an EcmParser in |parser| using |ecm|.
static std::unique_ptr<const EcmParser> Create(const CasEcm& ecm);
static std::unique_ptr<EcmParser> Create(const CasEcm& ecm);
// Accessor methods.
virtual uint8_t version() const = 0;
@@ -38,6 +38,9 @@ class EcmParser {
virtual std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const = 0;
virtual std::vector<uint8_t> content_iv(KeySlotId id) const = 0;
// Process group content keys instead of the normal content keys.
virtual bool set_group_id(const std::string& group_id) = 0;
virtual bool has_fingerprinting() const = 0;
virtual video_widevine::Fingerprinting fingerprinting() const = 0;
virtual bool has_service_blocking() const = 0;

View File

@@ -30,7 +30,7 @@ class EcmParserV2 : public EcmParser {
// successful returns true and constructs an EcmParserV2 in |parser| using
// |ecm|.
static bool create(const CasEcm& cas_ecm,
std::unique_ptr<const EcmParserV2>* parser);
std::unique_ptr<EcmParserV2>* parser);
// Accessor methods.
uint8_t version() const override;
@@ -44,6 +44,11 @@ class EcmParserV2 : public EcmParser {
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
std::vector<uint8_t> content_iv(KeySlotId id) const override;
// Group keys not supported in v2.
bool set_group_id(const std::string& group_id) override {
return group_id.empty();
};
// ECM v2 or under does not have these fields.
bool has_fingerprinting() const override { return false; }
video_widevine::Fingerprinting fingerprinting() const override {

View File

@@ -25,7 +25,7 @@ class EcmParserV3 : public EcmParser {
// |ecm| must be Widevine ECM v3 (or higher if compatible) without section
// header. Validates the ecm. If validations is successful returns an
// EcmParserV3, otherwise an nullptr.
static std::unique_ptr<const EcmParserV3> Create(const CasEcm& ecm);
static std::unique_ptr<EcmParserV3> Create(const CasEcm& ecm);
// Accessor methods.
uint8_t version() const override;
@@ -39,6 +39,8 @@ class EcmParserV3 : public EcmParser {
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
std::vector<uint8_t> content_iv(KeySlotId id) const override;
bool set_group_id(const std::string& group_id) override;
bool has_fingerprinting() const override;
video_widevine::Fingerprinting fingerprinting() const override;
bool has_service_blocking() const override;
@@ -53,6 +55,8 @@ class EcmParserV3 : public EcmParser {
video_widevine::EcmPayload ecm_payload);
video_widevine::SignedEcmPayload signed_ecm_payload_;
video_widevine::EcmPayload ecm_payload_;
video_widevine::EcmKeyData even_key_data_;
video_widevine::EcmKeyData odd_key_data_;
};
} // namespace wvcas

View File

@@ -10,28 +10,9 @@
#include <vector>
#include "OEMCryptoCAS.h"
#include "crypto_key.h"
namespace wvcas {
// LoadKeysParams mirrors the parameters in the OEMCrypto API. It's purpose is
// to allow OEMCrypto_LoadKeys to be mocked. OEMCrypto_LoadKeys takes 13
// parameters as of API V14. GoogleMock allows a maximum of 10.
struct LoadKeysParams {
OEMCrypto_SESSION session = 0;
const uint8_t* message = nullptr;
size_t message_length = 0;
const uint8_t* signature = nullptr;
size_t signature_length = 0;
OEMCrypto_Substring enc_mac_keys_iv = {};
OEMCrypto_Substring enc_mac_keys = {};
size_t num_keys = 0;
const OEMCrypto_KeyObject* key_array = nullptr;
OEMCrypto_Substring pst = {};
OEMCrypto_Substring srm_requirement = {};
OEMCrypto_LicenseType license_type = OEMCrypto_ContentLicense;
};
// InputStreamParams mirrors the parameters in OEMCrypto API. The
// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble
// takes 11 parameters as of API V15. GoogleMock allows a maximum of 10.
@@ -107,8 +88,6 @@ class OEMCryptoInterface {
size_t enc_session_key_length, const uint8_t* mac_key_context,
size_t mac_key_context_length, const uint8_t* enc_key_context,
size_t enc_key_context_length) const;
virtual OEMCryptoResult OEMCrypto_LoadKeys(
const LoadKeysParams& load_key_params) const;
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
@@ -130,10 +109,6 @@ class OEMCryptoInterface {
size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) const;
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const;
virtual OEMCryptoResult OEMCrypto_RefreshKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array);
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength);
virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer,

View File

@@ -54,8 +54,15 @@ class WidevineCas : public wvutil::TimerHandler {
std::string& license_id);
// Processes the entitlement |response| to a entitlement license request.
virtual CasStatus handleEntitlementResponse(const std::string& response,
std::string& license_id);
// |license_id| is the id of the license installed. Can be used to select
// which license to install.
// |multi_content_license_info| contains the message that can be sent to the
// app if the installed license is a multi content license.
// |group_license_info| contains the message that can be sent to the app if
// the installed license is a group license.
virtual CasStatus handleEntitlementResponse(
const std::string& response, std::string& license_id,
std::string& multi_content_license_info, std::string& group_license_info);
// Generates an entitlement license request in |entitlement_request| for the
// media described in |init_data|.
@@ -87,8 +94,11 @@ class WidevineCas : public wvutil::TimerHandler {
// Set the minimum age required to process ECM.
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
// Remove the in used license. If successful content id is returned.
virtual CasStatus RemoveLicense(const std::string file_name);
// Remove the license file given the filename user provides.
virtual CasStatus RemoveLicense(const std::string& file_name);
// Record the license id that user provides.
virtual CasStatus RecordLicenseId(const std::string& license_id);
void OnTimerEvent() override;
@@ -127,6 +137,15 @@ class WidevineCas : public wvutil::TimerHandler {
// The age_restriction field in ECM must be greater or equal to
// |parental_control_min_age|. Otherwise, ECM will stop being processed.
uint parental_control_age_ = 0;
// The assigned_license_id helps to indicate which license file current
// content will use if multiple licenses exist.
std::string assigned_license_id_;
// The current in use license_id.
std::string license_id_;
// The group id of a Group license. Empty if the license is not a Group
// license (multi content license is not a group license). Used in processECM
// to select group keys that can be decrypted by the license.
std::string license_group_id_;
}; // namespace wvcas
} // namespace wvcas

View File

@@ -49,16 +49,15 @@ class WidevineCasSession {
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* event_listener, uint32_t* session_id);
// Get the current key information. This method will be used by a descrambler
// plugin to obtain the current key information.
const KeySlot& key(KeySlotId slot_id) const;
// Process an ecm and extract the key slot data. Extracted data will be used
// to update |current_ecm_| and |entitlement_key_id_| and |keys_|.
// |parental_control_age| (if non-zero) must be greater or equal to the
// age_restriction field specified in |ecm|. Otherwise, ECM will not be
// processed and error will be returned.
virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age);
// |license_group_id| if non empty, processEcm will decrypt content keys that
// are specified by |license_group_id|.
virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age,
const std::string& license_group_id);
// Returns the security level retrieved from OEMCrypto.
const char* securityLevel();
@@ -71,8 +70,7 @@ class WidevineCasSession {
private:
// Creates an EcmParser.
virtual std::unique_ptr<const EcmParser> getEcmParser(
const CasEcm& ecm) const;
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
CasKeySlotData keys_; // Odd and even key slots.
std::string entitlement_key_id_;

View File

@@ -30,12 +30,9 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
// MediaCas platform api documentation.
WidevineCasPlugin(void* appData, CasPluginCallback callback);
WidevineCasPlugin(void* appData, CasPluginCallbackExt callback);
~WidevineCasPlugin() override {}
virtual ~WidevineCasPlugin() override {}
virtual status_t initialize();
// Returns true if the device has been provisioned with a device certificate.
bool is_provisioned();
status_t initialize();
// Provide a callback to report plugin status.
status_t setStatusCallback(CasPluginStatusCallback callback) override;
@@ -88,19 +85,28 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
WidevineCasPlugin(const WidevineCasPlugin&) = delete;
WidevineCasPlugin& operator=(const WidevineCasPlugin&) = delete;
protected:
// For unit test only.
virtual void SetWidevineCasApi(
std::unique_ptr<WidevineCas> widevine_cas_api) {
widevine_cas_api_ = std::move(widevine_cas_api);
}
WidevineCasPlugin(){};
private:
virtual std::shared_ptr<CryptoSession> getCryptoSession();
// |sessionId| is nullptr if the event is not a session event.
virtual CasStatus processEvent(int32_t event, int32_t arg,
const CasData& eventData,
const CasSessionId* sessionId);
virtual CasStatus HandleIndividualizationResponse(const CasData& response);
virtual CasStatus HandleEntitlementResponse(const CasData& response);
virtual status_t requestLicense(const std::string& init_data);
virtual CasStatus HandleEntitlementRenewalResponse(const CasData& response);
virtual CasStatus HandleUniqueIDQuery();
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
virtual CasStatus HandleLicenseRemoval(const CasData& license_id);
CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData,
const CasSessionId* sessionId);
CasStatus HandleIndividualizationResponse(const CasData& response);
CasStatus HandleEntitlementResponse(const CasData& response);
status_t requestLicense(const std::string& init_data);
CasStatus HandleEntitlementRenewalResponse(const CasData& response);
CasStatus HandleUniqueIDQuery();
CasStatus HandleSetParentalControlAge(const CasData& data);
CasStatus HandleLicenseRemoval(const CasData& license_id);
CasStatus HandleAssignLicenseID(const CasData& license_id);
// Returns true if the device has been provisioned with a device certificate.
bool is_provisioned() const;
// Event listener implementation
void OnSessionRenewalNeeded() override;
@@ -120,8 +126,9 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
// Choose to use |callback_| or |callback_ext_| to send back information.
// |sessionId| is ignored if |callback_ext_| is null,
void CallBack(void* appData, int32_t event, int32_t arg, uint8_t* data,
size_t size, const CasSessionId* sessionId) const;
virtual void CallBack(void* appData, int32_t event, int32_t arg,
uint8_t* data, size_t size,
const CasSessionId* sessionId) const;
void* app_data_;
CasPluginCallback callback_;
@@ -132,7 +139,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
// is used to build a PSSH, and others are discarded.
bool is_emm_request_sent_ = false;
std::string provision_data_;
WidevineCas widevine_cas_;
std::unique_ptr<WidevineCas> widevine_cas_api_;
};
} // namespace wvcas

View File

@@ -7,12 +7,13 @@
#include <openssl/sha.h>
#include <sys/time.h>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include "cas_properties.h"
#include "cas_util.h"
#include "crypto_key.h"
#include "crypto_session.h"
#include "device_files.pb.h"
#include "license_protocol.pb.h"
@@ -167,51 +168,6 @@ bool Hash(const std::string& data, std::string* hash) {
} // namespace
std::vector<CryptoKey> ExtractEntitlementKeys(const License& license) {
std::vector<CryptoKey> key_array;
key_array.reserve(license.key_size());
// Extract entitlement type key(s).
for (const auto& license_key : license.key()) {
if (license_key.type() == License_KeyContainer::ENTITLEMENT) {
key_array.emplace_back();
auto& key = key_array.back();
// Strip off PKCS#5 padding - since we know the key is 32 or 48 bytes,
// the padding will always be 16 bytes.
size_t length = 0;
if (license_key.key().size() > 32) {
length = license_key.key().size() - 16;
}
key.set_key_data(license_key.key().substr(0, length));
key.set_key_data_iv(license_key.iv());
key.set_key_id(license_key.id());
key.set_track_label(license_key.track_label());
if (license_key.has_key_control()) {
key.set_key_control(license_key.key_control().key_control_block());
key.set_key_control_iv(license_key.key_control().iv());
}
}
}
return key_array;
}
std::vector<CryptoKey> ExtractKeyControlKeys(const License& license) {
std::vector<CryptoKey> key_array;
key_array.reserve(license.key_size());
// Extract key control type key(s).
for (const auto& license_key : license.key()) {
if (license_key.type() == License_KeyContainer::KEY_CONTROL) {
key_array.emplace_back();
auto& key = key_array.back();
if (license_key.has_key_control()) {
key.set_key_control(license_key.key_control().key_control_block());
key.set_key_control_iv(license_key.key_control().iv());
}
key.set_track_label(license_key.track_label());
}
}
return key_array;
}
CasStatus GenerateLicenseFile(
const std::string& emm_request, const std::string& emm_response,
const std::string& renewal_request, const std::string& renewal_response,
@@ -477,8 +433,9 @@ CasStatus CasLicense::GenerateEntitlementRequest(
return CasStatusCode::kNoError;
}
CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
const std::string& license_file) {
CasStatus CasLicense::HandleStoredLicense(
const std::string& wrapped_rsa_key, const std::string& license_file,
const std::string* content_id_filter) {
HashedFile hash_file;
if (!hash_file.ParseFromString(license_file)) {
return CasStatus(CasStatusCode::kLicenseFileParseError,
@@ -545,7 +502,7 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
status = InstallLicense(signed_message.session_key(), signed_message.msg(),
signed_message.oemcrypto_core_message(),
signed_message.signature());
signed_message.signature(), content_id_filter);
if (!status.ok()) {
return status;
}
@@ -568,7 +525,8 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
}
CasStatus CasLicense::HandleEntitlementResponse(
const std::string& entitlement_response, std::string* device_file) {
const std::string& entitlement_response,
const std::string* content_id_filter, std::string* device_file) {
video_widevine::SignedMessage signed_message;
if (!signed_message.ParseFromString(entitlement_response)) {
return CasStatus(CasStatusCode::kCasLicenseError,
@@ -591,9 +549,10 @@ CasStatus CasLicense::HandleEntitlementResponse(
"no oemcrypto core message present");
}
CasStatus status = InstallLicense(
signed_message.session_key(), signed_message.msg(),
signed_message.oemcrypto_core_message(), signed_message.signature());
CasStatus status =
InstallLicense(signed_message.session_key(), signed_message.msg(),
signed_message.oemcrypto_core_message(),
signed_message.signature(), content_id_filter);
if (!status.ok()) {
return status;
}
@@ -721,7 +680,8 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const {
CasStatus CasLicense::InstallLicense(const std::string& session_key,
const std::string& serialized_license,
const std::string& core_message,
const std::string& signature) {
const std::string& signature,
const std::string* /*content_id_filter*/) {
video_widevine::License license;
if (!license.ParseFromString(serialized_license)) {
return CasStatus(CasStatusCode::kCasLicenseError,
@@ -768,12 +728,7 @@ CasStatus CasLicense::InstallLicense(const std::string& session_key,
mac_key_str.resize(2 * kMacKeySizeBytes);
}
std::vector<CryptoKey> key_array = ExtractEntitlementKeys(license);
if (key_array.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"the entitlement contains no keys");
}
// TODO: apply content_id_filter
status =
crypto_session_->LoadLicense(serialized_license, core_message, signature);
if (!status.ok()) {
@@ -856,6 +811,36 @@ bool CasLicense::CanStoreLicense() const {
return policy_engine_->CanPersist();
}
std::string CasLicense::GetGroupId() const {
return license_.license_category_spec().group_id();
}
std::vector<std::string> CasLicense::GetContentIdList() const {
std::set<std::string> content_ids;
if (IsMultiContentLicense()) {
for (const auto& license_key : license_.key()) {
if (license_key.type() == License_KeyContainer::ENTITLEMENT) {
if (license_key.key_category_spec().has_content_id()) {
content_ids.insert(license_key.key_category_spec().content_id());
} else if (license_key.key_category_spec().has_group_id()) {
content_ids.insert(license_key.key_category_spec().group_id());
}
}
}
}
return {content_ids.begin(), content_ids.end()};
}
bool CasLicense::IsMultiContentLicense() const {
return license_.license_category_spec().license_category() ==
video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE;
}
bool CasLicense::IsGroupLicense() const {
return license_.license_category_spec().license_category() ==
video_widevine::LicenseCategorySpec::GROUP_LICENSE;
}
CasStatus CasLicense::GenerateEntitlementRenewalRequest(
const std::string& device_certificate,
std::string* signed_renewal_request) {

View File

@@ -265,13 +265,6 @@ OEMCryptoResult CryptoInterface::OEMCrypto_DeriveKeysFromSessionKey(
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_LoadKeys(
const LoadKeysParams& load_key_params) {
return lock_->WithOecSessionLock("LoadKeys", [&] {
return oemcrypto_interface_->OEMCrypto_LoadKeys(load_key_params);
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_LoadLicense(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
@@ -320,17 +313,6 @@ OEMCryptoResult CryptoInterface::OEMCrypto_GetHDCPCapability(
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_RefreshKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array) {
return lock_->WithOecSessionLock("RefreshKeys", [&] {
return oemcrypto_interface_->OEMCrypto_RefreshKeys(
session, message, message_length, signature, signature_length, num_keys,
key_array);
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength) {
return lock_->WithOecReadLock("GetDeviceID", [&] {
@@ -828,64 +810,6 @@ CasStatus CryptoSession::DeriveKeysFromSessionKey(
return CasStatus::OkStatus();
}
CasStatus CryptoSession::LoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& key_array,
const std::string& pst,
const std::string& srm_requirement) {
if (key_array.empty()) {
return CasStatus::OkStatus();
}
LoadKeysParams load_key_params;
load_key_params.session = session_;
load_key_params.message = reinterpret_cast<const uint8_t*>(message.data());
load_key_params.message_length = message.size();
load_key_params.signature =
reinterpret_cast<const uint8_t*>(signature.data());
load_key_params.signature_length = signature.size();
load_key_params.enc_mac_keys_iv.offset = GetOffset(message, mac_key_iv);
load_key_params.enc_mac_keys_iv.length = mac_key_iv.size();
load_key_params.enc_mac_keys.offset = GetOffset(message, mac_key);
load_key_params.enc_mac_keys.length = mac_key.size();
load_key_params.pst.offset = GetOffset(message, pst);
load_key_params.pst.length = pst.size();
load_key_params.srm_requirement.offset = GetOffset(message, srm_requirement);
load_key_params.srm_requirement.length = srm_requirement.size();
std::vector<OEMCrypto_KeyObject> key_objects;
key_objects.reserve(key_array.size());
for (const auto& ki : key_array) {
key_objects.resize(key_objects.size() + 1);
OEMCrypto_KeyObject& ko = key_objects.back();
ko.key_id.offset = GetOffset(message, ki.key_id());
ko.key_id.length = ki.key_id().length();
ko.key_data_iv.offset = GetOffset(message, ki.key_data_iv());
ko.key_data_iv.length = ki.key_data_iv().length();
ko.key_data.offset = GetOffset(message, ki.key_data());
ko.key_data.length = ki.key_data().length();
if (ki.HasKeyControl()) {
ko.key_control_iv.offset = GetOffset(message, ki.key_control_iv());
ko.key_control_iv.length = ki.key_control_iv().length();
ko.key_control.offset = GetOffset(message, ki.key_control());
ko.key_control.length = ki.key_control().length();
}
}
load_key_params.key_array = &key_objects[0];
load_key_params.num_keys = key_objects.size();
load_key_params.license_type = OEMCrypto_EntitlementLicense;
OEMCryptoResult result =
crypto_interface_->OEMCrypto_LoadKeys(load_key_params);
if (result != OEMCrypto_SUCCESS) {
std::ostringstream err_string;
err_string << "OEMCrypto_LoadKeys returned " << result;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
return CasStatus::OkStatus();
}
CasStatus CryptoSession::LoadLicense(const std::string& signed_message,
const std::string& core_message,
const std::string& signature) {
@@ -1040,42 +964,6 @@ OEMCryptoResult CryptoSession::getCryptoInterface(
return CryptoInterface::create(interface);
}
CasStatus CryptoSession::RefreshKeys(const std::string& message,
const std::string& signature,
const std::vector<CryptoKey>& key_array) {
if (key_array.empty()) {
return CasStatus::OkStatus();
}
// Assume the message and signature parameters are valid.
const uint8_t* message_ptr = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* signature_ptr =
reinterpret_cast<const uint8_t*>(signature.data());
std::vector<OEMCrypto_KeyRefreshObject> load_key_array;
load_key_array.reserve(key_array.size());
for (const auto& key : key_array) {
load_key_array.emplace_back();
auto& key_object = load_key_array.back();
key_object.key_id.offset = GetOffset(message, key.key_id());
key_object.key_id.length = key.key_id().length();
if (!key.key_control().empty()) {
key_object.key_control_iv.offset =
GetOffset(message, key.key_control_iv());
key_object.key_control_iv.length = key.key_control_iv().length();
key_object.key_control.offset = GetOffset(message, key.key_control());
key_object.key_control.length = key.key_control().length();
}
}
OEMCryptoResult result = crypto_interface_->OEMCrypto_RefreshKeys(
session_, message_ptr, message.size(), signature_ptr, signature.size(),
load_key_array.size(), &load_key_array[0]);
if (result != OEMCrypto_SUCCESS) {
std::ostringstream err_string;
err_string << "OEMCrypto_RefreshKeys returned " << result;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
return CasStatus::OkStatus();
}
CasStatus CryptoSession::GetDeviceID(std::string* buffer) {
if (!crypto_interface_) {
return CasStatus(CasStatusCode::kCryptoSessionError,

View File

@@ -51,7 +51,7 @@ int find_ecm_start_index(const CasEcm& cas_ecm) {
} // namespace
std::unique_ptr<const EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
std::unique_ptr<EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
// Detect and strip optional section header.
const int offset = find_ecm_start_index(cas_ecm);
if (offset < 0 ||
@@ -71,7 +71,7 @@ std::unique_ptr<const EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
}
if (ecm[kVersionIndex] <= 2) {
std::unique_ptr<const EcmParserV2> parser;
std::unique_ptr<EcmParserV2> parser;
if (!EcmParserV2::create(ecm, &parser)) {
return nullptr;
}

View File

@@ -80,13 +80,12 @@ const EcmKeyData* EcmParserV2::key_slot_data(KeySlotId id) const {
}
bool EcmParserV2::create(const CasEcm& cas_ecm,
std::unique_ptr<const EcmParserV2>* parser) {
std::unique_ptr<EcmParserV2>* parser) {
if (parser == nullptr) {
return false;
}
// Using 'new' to access a non-public constructor.
auto new_parser =
std::unique_ptr<const EcmParserV2>(new EcmParserV2(cas_ecm));
auto new_parser = std::unique_ptr<EcmParserV2>(new EcmParserV2(cas_ecm));
if (!new_parser->is_valid_size()) {
return false;
}

View File

@@ -51,9 +51,12 @@ CryptoMode ConvertProtoCipherMode(EcmMetaData::CipherMode cipher_mode) {
EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload,
EcmPayload ecm_payload)
: signed_ecm_payload_(std::move(signed_ecm_payload)),
ecm_payload_(std::move(ecm_payload)) {}
ecm_payload_(std::move(ecm_payload)) {
even_key_data_ = ecm_payload_.even_key_data();
odd_key_data_ = ecm_payload_.odd_key_data();
}
std::unique_ptr<const EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
std::unique_ptr<EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
if (cas_ecm.size() <= kEcmHeaderSize) {
LOGE("ECM is too short. Size: %u", cas_ecm.size());
return nullptr;
@@ -74,7 +77,7 @@ std::unique_ptr<const EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
}
// Using 'new' to access a non-public constructor.
return std::unique_ptr<const EcmParserV3>(
return std::unique_ptr<EcmParserV3>(
new EcmParserV3(signed_ecm_payload, ecm_payload));
}
@@ -100,9 +103,9 @@ std::vector<uint8_t> EcmParserV3::entitlement_key_id(KeySlotId id) const {
// Use the even entitlement_key_id if the odd one is empty (omitted).
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().entitlement_key_id().empty()
? ecm_payload_.odd_key_data()
: ecm_payload_.even_key_data();
!odd_key_data_.entitlement_key_id().empty()
? odd_key_data_
: even_key_data_;
return {key_data.entitlement_key_id().begin(),
key_data.entitlement_key_id().end()};
@@ -118,9 +121,8 @@ std::vector<uint8_t> EcmParserV3::content_key_id(KeySlotId id) const {
}
std::vector<uint8_t> EcmParserV3::wrapped_key_data(KeySlotId id) const {
const EcmKeyData& key_data = id == KeySlotId::kOddKeySlot
? ecm_payload_.odd_key_data()
: ecm_payload_.even_key_data();
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot ? odd_key_data_ : even_key_data_;
return {key_data.wrapped_key_data().begin(),
key_data.wrapped_key_data().end()};
@@ -128,24 +130,36 @@ std::vector<uint8_t> EcmParserV3::wrapped_key_data(KeySlotId id) const {
std::vector<uint8_t> EcmParserV3::wrapped_key_iv(KeySlotId id) const {
// Use the even wrapped_key_iv if the odd one is empty (omitted).
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().wrapped_key_iv().empty()
? ecm_payload_.odd_key_data()
: ecm_payload_.even_key_data();
const EcmKeyData* key_data =
id == KeySlotId::kOddKeySlot && !odd_key_data_.wrapped_key_iv().empty()
? &odd_key_data_
: &even_key_data_;
// Wrapped key IV may be omitted for group keys.
if (key_data->wrapped_key_iv().empty()) {
key_data = id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().wrapped_key_iv().empty()
? &ecm_payload_.odd_key_data()
: &ecm_payload_.even_key_data();
}
return {key_data.wrapped_key_iv().begin(), key_data.wrapped_key_iv().end()};
return {key_data->wrapped_key_iv().begin(), key_data->wrapped_key_iv().end()};
}
std::vector<uint8_t> EcmParserV3::content_iv(KeySlotId id) const {
// Use the even content_iv if the odd one is empty (omitted).
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().content_iv().empty()
? ecm_payload_.odd_key_data()
: ecm_payload_.even_key_data();
const EcmKeyData* key_data =
id == KeySlotId::kOddKeySlot && !odd_key_data_.content_iv().empty()
? &odd_key_data_
: &even_key_data_;
// Content IV may be omitted for group keys.
if (key_data->content_iv().empty()) {
key_data = id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().content_iv().empty()
? &ecm_payload_.odd_key_data()
: &ecm_payload_.even_key_data();
}
return {key_data.content_iv().begin(), key_data.content_iv().end()};
return {key_data->content_iv().begin(), key_data->content_iv().end()};
}
bool EcmParserV3::has_fingerprinting() const {
@@ -171,4 +185,24 @@ std::string EcmParserV3::signature() const {
return signed_ecm_payload_.signature();
}
bool EcmParserV3::set_group_id(const std::string& group_id) {
if (group_id.empty()) {
even_key_data_ = ecm_payload_.even_key_data();
odd_key_data_ = ecm_payload_.odd_key_data();
}
bool found = false;
for (int i = 0; i < ecm_payload_.group_key_data_size(); ++i) {
const video_widevine::EcmGroupKeyData& group_key_data =
ecm_payload_.group_key_data(i);
if (group_key_data.group_id() == group_id) {
found = true;
even_key_data_ = group_key_data.even_key_data();
odd_key_data_ = group_key_data.odd_key_data();
break;
}
}
return found;
}
} // namespace wvcas

View File

@@ -81,11 +81,6 @@ class OEMCryptoInterface::Impl {
const uint8_t*, size_t,
const uint8_t*, size_t,
const uint8_t*, size_t);
typedef OEMCryptoResult (*LoadKeys_t)(
OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, size_t,
OEMCrypto_Substring, OEMCrypto_Substring, size_t,
const OEMCrypto_KeyObject*, OEMCrypto_Substring, OEMCrypto_Substring,
OEMCrypto_LicenseType);
typedef OEMCryptoResult (*LoadLicense_t)(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
@@ -106,10 +101,6 @@ class OEMCryptoInterface::Impl {
size_t, OEMCryptoCipherMode);
typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*,
OEMCrypto_HDCP_Capability*);
typedef OEMCryptoResult (*RefreshKeys_t)(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array);
typedef OEMCryptoResult (*GetDeviceID_t)(uint8_t* deviceID, size_t* idLength);
typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer,
size_t length);
@@ -137,13 +128,11 @@ class OEMCryptoInterface::Impl {
LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr;
GenerateRSASignature_t GenerateRSASignature = nullptr;
DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr;
LoadKeys_t LoadKeys = nullptr;
LoadLicense_t LoadLicense = nullptr;
LoadRenewal_t LoadRenewal = nullptr;
LoadCasECMKeys_t LoadCasECMKeys = nullptr;
SelectKey_t SelectKey = nullptr;
GetHDCPCapability_t GetHDCPCapability = nullptr;
RefreshKeys_t RefreshKeys = nullptr;
GetDeviceID_t GetDeviceID = nullptr;
LoadTestKeybox_t LoadTestKeybox = nullptr;
SecurityLevel_t SecurityLevel = nullptr;
@@ -182,13 +171,11 @@ class OEMCryptoInterface::Impl {
LOAD_SYM(LoadDRMPrivateKey);
LOAD_SYM(GenerateRSASignature);
LOAD_SYM(DeriveKeysFromSessionKey);
LOAD_SYM(LoadKeys);
LOAD_SYM(LoadLicense);
LOAD_SYM(LoadRenewal);
LOAD_SYM(LoadCasECMKeys);
LOAD_SYM(SelectKey);
LOAD_SYM(GetHDCPCapability);
LOAD_SYM(RefreshKeys);
LOAD_SYM(GetDeviceID);
LOAD_SYM(SecurityLevel);
LOAD_SYM(CreateEntitledKeySession);
@@ -328,17 +315,6 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_DeriveKeysFromSessionKey(
mac_key_context_length, enc_key_context, enc_key_context_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadKeys(
const LoadKeysParams& load_key_params) const {
return impl_->LoadKeys(
load_key_params.session, load_key_params.message,
load_key_params.message_length, load_key_params.signature,
load_key_params.signature_length, load_key_params.enc_mac_keys_iv,
load_key_params.enc_mac_keys, load_key_params.num_keys,
load_key_params.key_array, load_key_params.pst,
load_key_params.srm_requirement, load_key_params.license_type);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadLicense(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
@@ -375,14 +351,6 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability(
return impl_->GetHDCPCapability(current, max);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_RefreshKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length, size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array) {
return impl_->RefreshKeys(session, message, message_length, signature,
signature_length, num_keys, key_array);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength) {
return impl_->GetDeviceID(deviceID, idLength);

View File

@@ -76,6 +76,46 @@ std::string GenerateLicenseFilename(const std::string& content_id,
return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) +
std::string(kLicenseFileNameSuffix));
}
std::string GenerateMultiContentLicenseInfo(
const std::string& license_id,
const std::vector<std::string>& content_list) {
std::string message;
if (license_id.empty() || content_list.empty()) {
return message;
}
message.push_back(MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_ID);
message.push_back((license_id.size() >> 8) & 0xff);
message.push_back(license_id.size() & 0xff);
message.append(license_id);
for (const auto& content_id : content_list) {
message.push_back(
MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_CONTENT_ID);
message.push_back((content_id.size() >> 8) & 0xff);
message.push_back(content_id.size() & 0xff);
message.append(content_id);
}
return message;
}
std::string GenerateGroupLicenseInfo(const std::string& license_id,
const std::string group_id) {
std::string message;
if (license_id.empty() || group_id.empty()) {
return message;
}
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_ID);
message.push_back((license_id.size() >> 8) & 0xff);
message.push_back(license_id.size() & 0xff);
message.append(license_id);
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_GROUP_ID);
message.push_back((group_id.size() >> 8) & 0xff);
message.push_back(group_id.size() & 0xff);
message.append(group_id);
return message;
}
} // namespace
namespace wvcas {
@@ -129,8 +169,7 @@ void WidevineCas::OnTimerEvent() {
// Delete expired license after firing expired event in policy_engine
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
std::string filename = license_id_ + kLicenseFileNameSuffix;
if (!file_system_->Exists(filename)) {
LOGI("No expired license file stored in disk");
} else {
@@ -238,7 +277,8 @@ CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId,
}
uint8_t ecm_age_previous = session->GetEcmAgeRestriction();
CasStatus status = session->processEcm(ecm, parental_control_age_);
CasStatus status =
session->processEcm(ecm, parental_control_age_, license_group_id_);
uint8_t ecm_age_current = session->GetEcmAgeRestriction();
if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) {
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
@@ -302,12 +342,24 @@ CasStatus WidevineCas::generateEntitlementRequest(
if (!status.ok()) {
return status;
}
std::string filename;
// Backward compatible. If the license_filename is unassigned by app, plugin
// will directly use the single_content_license named "content_id +
// provider_id" by default.
if (assigned_license_id_.empty()) {
filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
} else {
filename = assigned_license_id_ + kLicenseFileNameSuffix;
// Clean up the assigned_license_filename for next round use.
assigned_license_id_.clear();
}
std::string license_file;
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
if (ReadFileFromStorage(*file_system_, filename, &license_file)) {
CasStatus status =
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
status = cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file,
/*content_id_filter=*/nullptr);
if (status.ok()) {
// If license file is expired, don't proceed the request. Also
// delete the stored license file.
@@ -323,6 +375,13 @@ CasStatus WidevineCas::generateEntitlementRequest(
}
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
if (cas_license_->IsGroupLicense()) {
license_group_id_ = cas_license_->GetGroupId();
}
// Save current in use license_id. The purpose is to make the license_id
// available for license removal or license expiration.
license_id_ = license_id;
policy_timer_.Start(this, 1);
has_license_ = true;
return HandleDeferredECMs();
@@ -340,20 +399,29 @@ CasStatus WidevineCas::generateEntitlementRequest(
entitlement_request);
}
CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
std::string& license_id) {
CasStatus WidevineCas::handleEntitlementResponse(
const std::string& response, std::string& license_id,
std::string& multi_content_license_info, std::string& group_license_info) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty entitlement response");
}
if (media_id_ == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
}
std::string device_file;
std::unique_lock<std::mutex> locker(lock_);
CasStatus status =
cas_license_->HandleEntitlementResponse(response, &device_file);
CasStatus status = cas_license_->HandleEntitlementResponse(
response, /*content_id_filter=*/nullptr, &device_file);
if (status.ok()) {
// A license has been successfully loaded. Load any ecms that may have been
// deferred waiting for the license.
if (cas_license_->IsGroupLicense()) {
license_group_id_ = cas_license_->GetGroupId();
}
has_license_ = true;
status = HandleDeferredECMs();
if (!status.ok()) {
return status;
@@ -361,17 +429,27 @@ CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
policy_timer_.Start(this, 1);
if (device_file.empty()) {
return status;
}
if (!device_file.empty()) {
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
const std::string license_group_id = cas_license_->GetGroupId();
std::string filename = GenerateLicenseFilename(
license_group_id.empty() ? media_id_->content_id() : license_group_id,
media_id_->provider_id());
StoreFile(*file_system_, filename, device_file);
// license_id will be the filename without ".lic" extension.
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
// Save the license id.
license_id_ = license_id;
// License info is only needed if the license is stored.
if (cas_license_->IsMultiContentLicense()) {
multi_content_license_info = GenerateMultiContentLicenseInfo(
license_id, cas_license_->GetContentIdList());
}
if (cas_license_->IsGroupLicense()) {
group_license_info =
GenerateGroupLicenseInfo(license_id, license_group_id);
}
}
}
return status;
@@ -400,21 +478,25 @@ CasStatus WidevineCas::handleEntitlementRenewalResponse(
if (!status.ok()) {
return status;
}
if (!device_file.empty()) {
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
if (!device_file.empty() && media_id_ != nullptr) {
const std::string license_group_id = cas_license_->GetGroupId();
std::string filename = GenerateLicenseFilename(
license_group_id.empty() ? media_id_->content_id() : license_group_id,
media_id_->provider_id());
StoreFile(*file_system_, filename, device_file);
// TODO(chelu): The license id should not change, right?
// license_id will be the filename without ".lic" extension.
license_id =
filename.substr(0, filename.size() - std::string(".lic").size());
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
license_id_ = license_id;
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::RemoveLicense(const std::string file_name) {
CasStatus WidevineCas::RemoveLicense(const std::string& file_name) {
// Check if the license is in use. If it is, besides removing the license,
// update policy in current license. Else, we just directly remove it.
if (nullptr == media_id_.get()) {
if (media_id_ == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
}
// Remove the license file given the file_name user provides.
@@ -423,9 +505,9 @@ CasStatus WidevineCas::RemoveLicense(const std::string file_name) {
"unable to remove license file from disk");
}
LOGI("Remove license file from disk successfully.");
std::string used_license_filename = GenerateLicenseFilename(
media_id_->content_id(), media_id_->provider_id());
if (file_name.compare(used_license_filename) == 0) {
std::string current_license_filename = license_id_ + kLicenseFileNameSuffix;
if (file_name == current_license_filename) {
// Update license policy for the in-used license. Plugin will not allowed to
// play stream, store and renew license unless a new plugin instance is
// created.
@@ -491,7 +573,13 @@ CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) {
"missing value of parental control min age");
}
parental_control_age_ = data[0];
LOGI("Parental control age set to: ", parental_control_age_);
LOGI("Parental control age set to: %d", parental_control_age_);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::RecordLicenseId(const std::string& license_id) {
assigned_license_id_ = license_id;
LOGI("License id selected is: %s", assigned_license_id_.c_str());
return CasStatusCode::kNoError;
}

View File

@@ -42,21 +42,19 @@ CasStatus WidevineCasSession::initialize(
return CasStatusCode::kNoError;
}
const KeySlot& WidevineCasSession::key(KeySlotId slot_id) const {
// TODO(): Make this function private and assume the mutex is locked.
// std::unique_lock<std::mutex> lock(lock_);
const KeySlot& key_slot = keys_[slot_id];
return key_slot;
}
CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
uint8_t parental_control_age) {
uint8_t parental_control_age,
const std::string& license_group_id) {
if (ecm != current_ecm_) {
LOGD("WidevineCasSession::processEcm: received new ecm");
std::unique_ptr<const EcmParser> ecm_parser = getEcmParser(ecm);
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
if (ecm_parser == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
}
if (!license_group_id.empty() &&
!ecm_parser->set_group_id(license_group_id)) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid group id");
}
ecm_age_restriction_ = ecm_parser->age_restriction();
// Parental control check.
@@ -114,7 +112,8 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
// Temporary key slots to only have successfully loaded keys in |keys_|.
CasKeySlotData keys;
do {
if (keys_[keyslot_id].key_id != ecm_parser->content_key_id(keyslot_id)) {
if (keys_[keyslot_id].wrapped_key !=
ecm_parser->wrapped_key_data(keyslot_id)) {
KeySlot& key = keys[keyslot_id];
key.key_id = ecm_parser->content_key_id(keyslot_id);
key.wrapped_key = ecm_parser->wrapped_key_data(keyslot_id);
@@ -161,7 +160,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
return CasStatusCode::kNoError;
}
std::unique_ptr<const EcmParser> WidevineCasSession::getEcmParser(
std::unique_ptr<EcmParser> WidevineCasSession::getEcmParser(
const CasEcm& ecm) const {
return EcmParser::Create(ecm);
}

View File

@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <time.h>
#include <utils/String8.h>
#include <iostream>
#include <memory>
@@ -54,22 +55,28 @@ CasSessionId widevineSessionIdToAndroid(const WvCasSessionId& wv_session_id) {
}
WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback)
: app_data_(appData), callback_(callback), callback_ext_(nullptr) {}
: app_data_(appData),
callback_(callback),
callback_ext_(nullptr),
widevine_cas_api_(make_unique<WidevineCas>()) {}
WidevineCasPlugin::WidevineCasPlugin(void* appData,
CasPluginCallbackExt callback)
: app_data_(appData), callback_(nullptr), callback_ext_(callback) {}
: app_data_(appData),
callback_(nullptr),
callback_ext_(callback),
widevine_cas_api_(make_unique<WidevineCas>()) {}
status_t WidevineCasPlugin::initialize() {
CasStatus status = widevine_cas_.initialize(this);
CasStatus status = widevine_cas_api_->initialize(this);
if (!status.ok()) {
return android::ERROR_CAS_UNKNOWN;
}
return OK;
}
bool WidevineCasPlugin::is_provisioned() {
return widevine_cas_.is_provisioned();
bool WidevineCasPlugin::is_provisioned() const {
return widevine_cas_api_->is_provisioned();
}
status_t WidevineCasPlugin::setStatusCallback(
@@ -86,11 +93,11 @@ status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
return OK;
}
CasStatus status =
widevine_cas_.ProcessCAPrivateData(privateData, &provision_data_);
widevine_cas_api_->ProcessCAPrivateData(privateData, &provision_data_);
if (!status.ok()) {
return android::ERROR_CAS_UNKNOWN;
}
if (widevine_cas_.is_provisioned()) {
if (widevine_cas_api_->is_provisioned()) {
return requestLicense(provision_data_);
}
return OK;
@@ -100,8 +107,13 @@ status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) {
if (nullptr == sessionId) {
return BAD_VALUE;
}
if (!is_provisioned()) {
LOGE("Sessions can only be opened after privisioned.");
return android::ERROR_CAS_NOT_PROVISIONED;
}
WvCasSessionId new_session_id;
CasStatus status = widevine_cas_.openSession(&new_session_id);
CasStatus status = widevine_cas_api_->openSession(&new_session_id);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
@@ -123,7 +135,7 @@ status_t WidevineCasPlugin::openSession(uint32_t intent, uint32_t mode,
status_t WidevineCasPlugin::closeSession(const CasSessionId& sessionId) {
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
CasStatus status = widevine_cas_.closeSession(wv_session_id);
CasStatus status = widevine_cas_api_->closeSession(wv_session_id);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
@@ -140,12 +152,12 @@ status_t WidevineCasPlugin::setSessionPrivateData(const CasSessionId& sessionId,
// Doesn't matter which session, CA descriptor applies to all of them.
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
CasStatus status = widevine_cas_.ProcessSessionCAPrivateData(
CasStatus status = widevine_cas_api_->ProcessSessionCAPrivateData(
wv_session_id, privateData, &provision_data_);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
if (widevine_cas_.is_provisioned()) {
if (widevine_cas_api_->is_provisioned()) {
return requestLicense(provision_data_);
}
return OK;
@@ -155,7 +167,7 @@ status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
const CasEcm& ecm) {
LOGI("WidevineCasPlugin::processEcm");
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
CasStatus status = widevine_cas_.processEcm(wv_session_id, ecm);
CasStatus status = widevine_cas_api_->processEcm(wv_session_id, ecm);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
switch (status.status_code()) {
@@ -227,8 +239,8 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
}
std::string provisioning_request;
CasStatus status =
widevine_cas_.generateDeviceProvisioningRequest(&provisioning_request);
CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest(
&provisioning_request);
if (!status.ok()) {
return INVALID_OPERATION;
}
@@ -246,7 +258,7 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
std::string signed_license_request;
std::string license_id;
CasStatus status = widevine_cas_.generateEntitlementRequest(
CasStatus status = widevine_cas_api_->generateEntitlementRequest(
init_data, &signed_license_request, license_id);
if (!status.ok()) {
return INVALID_OPERATION;
@@ -273,10 +285,6 @@ status_t WidevineCasPlugin::refreshEntitlements(
return OK;
}
std::shared_ptr<CryptoSession> WidevineCasPlugin::getCryptoSession() {
return std::make_shared<CryptoSession>();
}
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
const CasData& eventData,
const CasSessionId* sessionId) {
@@ -297,6 +305,8 @@ CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
return HandleSetParentalControlAge(eventData);
case LICENSE_REMOVAL:
return HandleLicenseRemoval(eventData);
case ASSIGN_LICENSE_ID:
return HandleAssignLicenseID(eventData);
default:
return CasStatusCode::kUnknownEvent;
}
@@ -310,7 +320,7 @@ CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
"empty individualization response");
}
std::string resp_string(response.begin(), response.end());
CasStatus status = widevine_cas_.handleProvisioningResponse(resp_string);
CasStatus status = widevine_cas_api_->handleProvisioningResponse(resp_string);
if (!status.ok()) {
return status;
}
@@ -335,14 +345,28 @@ CasStatus WidevineCasPlugin::HandleEntitlementResponse(
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
CasStatus status =
widevine_cas_.handleEntitlementResponse(resp_string, license_id);
std::string multi_content_license_info;
std::string group_license_info;
CasStatus status = widevine_cas_api_->handleEntitlementResponse(
resp_string, license_id, multi_content_license_info, group_license_info);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
license_id.size(), nullptr);
if (!multi_content_license_info.empty()) {
CallBack(reinterpret_cast<void*>(app_data_), MULTI_CONTENT_LICENSE_INFO,
MULTI_CONTENT_LICENSE_INFO,
reinterpret_cast<uint8_t*>(&multi_content_license_info[0]),
multi_content_license_info.size(), nullptr);
}
if (!group_license_info.empty()) {
CallBack(reinterpret_cast<void*>(app_data_), GROUP_LICENSE_INFO,
GROUP_LICENSE_INFO,
reinterpret_cast<uint8_t*>(&group_license_info[0]),
group_license_info.size(), nullptr);
}
return CasStatusCode::kNoError;
}
@@ -353,8 +377,8 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
CasStatus status =
widevine_cas_.handleEntitlementRenewalResponse(resp_string, license_id);
CasStatus status = widevine_cas_api_->handleEntitlementRenewalResponse(
resp_string, license_id);
if (!status.ok()) {
return status;
}
@@ -367,7 +391,7 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
std::string buffer;
CasStatus status = widevine_cas_.GetUniqueID(&buffer);
CasStatus status = widevine_cas_api_->GetUniqueID(&buffer);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
@@ -383,7 +407,7 @@ CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
}
CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
return widevine_cas_.HandleSetParentalControlAge(data);
return widevine_cas_api_->HandleSetParentalControlAge(data);
}
CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
@@ -393,7 +417,7 @@ CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
std::string license_id_str(license_id.begin(), license_id.end());
std::string file_name = license_id_str + ".lic";
CasStatus status = widevine_cas_.RemoveLicense(file_name);
CasStatus status = widevine_cas_api_->RemoveLicense(file_name);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
@@ -407,11 +431,27 @@ CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleAssignLicenseID(
const wvcas::CasData& license_id) {
if (license_id.empty()) {
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
}
std::string license_id_str(license_id.begin(), license_id.end());
CasStatus status = widevine_cas_api_->RecordLicenseId(license_id_str);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_ID_ASSIGNED,
LICENSE_ID_ASSIGNED, reinterpret_cast<uint8_t*>(&license_id_str[0]),
license_id_str.size(), nullptr);
return CasStatusCode::kNoError;
}
void WidevineCasPlugin::OnSessionRenewalNeeded() {
LOGI("OnSessionRenewalNeeded");
std::string renewal_request;
CasStatus status =
widevine_cas_.generateEntitlementRenewalRequest(&renewal_request);
widevine_cas_api_->generateEntitlementRenewalRequest(&renewal_request);
if (!status.ok()) {
LOGE("unable to generate a license renewal request: %s",
status.error_string().c_str());
@@ -435,8 +475,15 @@ void WidevineCasPlugin::OnSessionKeysChange(const KeyStatusMap& keys_status,
}
}
// Send the license expiration timestamp via this existing callback to app.
// Callback will be triggered once license is installed or license expiration
// time getting update.
// TODO(b/163427255): Should we combine with license_id?
void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) {
LOGI("OnExpirationUpdate");
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_NEW_EXPIRY_TIME,
LICENSE_NEW_EXPIRY_TIME,
reinterpret_cast<uint8_t*>(&new_expiry_time_seconds), 8, nullptr);
}
void WidevineCasPlugin::OnNewRenewalServerUrl(