Cas Client repo update-2.
-Parse EMM in Cas plugin -Entitlement key rotation support -Multi_content_license support
This commit is contained in:
@@ -12,6 +12,7 @@ cc_library_static {
|
||||
"src/ecm_parser.cpp",
|
||||
"src/ecm_parser_v2.cpp",
|
||||
"src/ecm_parser_v3.cpp",
|
||||
"src/emm_parser.cpp",
|
||||
"src/license_key_status.cpp",
|
||||
"src/oemcrypto_interface.cpp",
|
||||
"src/policy_engine.cpp",
|
||||
|
||||
@@ -37,6 +37,9 @@ typedef enum {
|
||||
LICENSE_NEW_EXPIRY_TIME,
|
||||
MULTI_CONTENT_LICENSE_INFO,
|
||||
GROUP_LICENSE_INFO,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
|
||||
// TODO(jfore): Evaluate removing this event in favor of return status codes
|
||||
// from
|
||||
@@ -49,6 +52,8 @@ typedef enum {
|
||||
|
||||
UNIQUE_ID = CAS_QUERY_EVENT_START,
|
||||
QUERY_UNIQUE_ID,
|
||||
WV_CAS_PLUGIN_VERSION,
|
||||
QUERY_WV_CAS_PLUGIN_VERSION,
|
||||
|
||||
SET_PARENTAL_CONTROL_AGE = CAS_PARENTAL_CONTROL_EVENT_START,
|
||||
DEPRECATED_PARENTAL_CONTROL_AGE_UPDATED,
|
||||
@@ -82,14 +87,30 @@ typedef enum {
|
||||
ECHO, // Respond to TEST_FOR_ECHO.
|
||||
} CasEventId;
|
||||
|
||||
// Types used inside an FINGERPRINTING_INFO event.
|
||||
typedef enum {
|
||||
FINGERPRINTING_CHANNEL = 0,
|
||||
FINGERPRINTING_CONTROL,
|
||||
} FingerprintingFieldType;
|
||||
|
||||
// Types used inside an SERVICE_BLOCKING_INFO event.
|
||||
typedef enum {
|
||||
SERVICE_BLOCKING_CHANNEL = 0,
|
||||
SERVICE_BLOCKING_DEVICE_GROUP,
|
||||
// Epoch time in seconds. Missing of this field or a value of 0 means
|
||||
// immediate start.
|
||||
SERVICE_BLOCKING_START_TIME_SECONDS,
|
||||
SERVICE_BLOCKING_END_TIME_SECONDS, // Epoch time in seconds.
|
||||
} ServiceBlockingFieldType;
|
||||
|
||||
// Types used inside an SESSION_FINGERPRINTING_CONTROL event.
|
||||
typedef enum {
|
||||
FINGERPRINTING_CONTROL = 0,
|
||||
SESSION_FINGERPRINTING_CONTROL = 0,
|
||||
} SessionFingerprintingFieldType;
|
||||
|
||||
// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event.
|
||||
typedef enum {
|
||||
SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
||||
SESSION_SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
||||
} SessionServiceBlockingFieldType;
|
||||
|
||||
// Types used inside a MULTI_CONTENT_LICENSE_INFO event.
|
||||
|
||||
@@ -53,22 +53,16 @@ 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* content_id_filter);
|
||||
const std::string& license_file);
|
||||
|
||||
// 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,
|
||||
const std::string* content_id_filter, std::string* device_file);
|
||||
const std::string& entitlement_response, std::string* device_file);
|
||||
|
||||
// Process a previously stored device |certificate| and make it available
|
||||
// for use in an EMM request.
|
||||
@@ -132,6 +126,8 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
|
||||
|
||||
void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) override;
|
||||
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
|
||||
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
|
||||
|
||||
void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
|
||||
const CasData& fingerprinting) override;
|
||||
@@ -140,12 +136,20 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
|
||||
const WvCasSessionId& sessionId,
|
||||
const CasData& service_blocking) override;
|
||||
|
||||
void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) override;
|
||||
|
||||
// Query to see if the license is expired.
|
||||
virtual bool IsExpired() const;
|
||||
|
||||
// Notify the license that playback decryption has begun.
|
||||
virtual void BeginDecryption();
|
||||
|
||||
// Returns NoError if a valid entitlement period index exists in
|
||||
// |license_file|. The index will be assigned to |entitlement_period_index|.
|
||||
static CasStatus GetEntitlementPeriodIndexFromStoredLicense(
|
||||
const std::string& license_file, uint32_t& entitlement_period_index);
|
||||
|
||||
CasLicense(const CasLicense&) = delete;
|
||||
CasLicense& operator=(const CasLicense&) = delete;
|
||||
|
||||
@@ -156,8 +160,7 @@ 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* content_id_filter);
|
||||
const std::string& signature);
|
||||
CasStatus InstallLicenseRenewal(const std::string& serialized_license,
|
||||
const std::string& core_message,
|
||||
const std::string& signature);
|
||||
|
||||
@@ -15,6 +15,9 @@ class CasMediaId {
|
||||
virtual CasStatus initialize(const std::string& init_data) = 0;
|
||||
virtual const std::string content_id() = 0;
|
||||
virtual const std::string provider_id() = 0;
|
||||
virtual bool is_entitlement_rotation_enabled() { return false; }
|
||||
virtual uint32_t entitlement_period_index() = 0;
|
||||
virtual std::string get_init_data() = 0;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -89,14 +89,23 @@ class CasEventListener {
|
||||
virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) = 0;
|
||||
|
||||
// Notifies listeners of new fingerprinting info.
|
||||
// Notifies listeners of new session fingerprinting info.
|
||||
virtual void OnSessionFingerprintingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0;
|
||||
|
||||
// Notifies listeners of new service blocking info.
|
||||
// Notifies listeners of new session service blocking info.
|
||||
virtual void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) = 0;
|
||||
|
||||
// Notifies listeners of new fingerprinting info.
|
||||
virtual void OnFingerprintingUpdated(const CasData& fingerprinting) = 0;
|
||||
|
||||
// Notifies listeners of new service blocking info.
|
||||
virtual void OnServiceBlockingUpdated(const CasData& service_blocking) = 0;
|
||||
|
||||
virtual void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) = 0;
|
||||
|
||||
CasEventListener(const CasEventListener&) = delete;
|
||||
CasEventListener& operator=(const CasEventListener&) = delete;
|
||||
};
|
||||
|
||||
@@ -172,6 +172,8 @@ class CryptoInterface {
|
||||
OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id);
|
||||
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid);
|
||||
virtual uint32_t OEMCrypto_APIVersion();
|
||||
|
||||
// This is the factory method used to enable the oemcrypto interface.
|
||||
@@ -284,6 +286,8 @@ class CryptoSession {
|
||||
OEMCrypto_SESSION* entitled_key_session_id);
|
||||
virtual CasStatus RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual CasStatus ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual CasStatus APIVersion(uint32_t* api_version);
|
||||
|
||||
CryptoSession(const CryptoSession&) = delete;
|
||||
|
||||
@@ -48,6 +48,10 @@ class EcmParser {
|
||||
// The serialized payload that the signature is calculated on.
|
||||
virtual std::string ecm_serialized_payload() const = 0;
|
||||
virtual std::string signature() const = 0;
|
||||
|
||||
virtual bool is_entitlement_rotation_enabled() const = 0;
|
||||
virtual uint32_t entitlement_period_index() const = 0;
|
||||
virtual uint32_t entitlement_rotation_window_left() const = 0;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -63,6 +63,10 @@ class EcmParserV2 : public EcmParser {
|
||||
std::string ecm_serialized_payload() const override { return ""; }
|
||||
std::string signature() const override { return ""; }
|
||||
|
||||
bool is_entitlement_rotation_enabled() const override { return false; }
|
||||
uint32_t entitlement_period_index() const override { return 0; }
|
||||
uint32_t entitlement_rotation_window_left() const override { return 0; }
|
||||
|
||||
private:
|
||||
// Constructs an EcmParserV2 using |ecm|.
|
||||
explicit EcmParserV2(const CasEcm& ecm);
|
||||
|
||||
@@ -49,6 +49,10 @@ class EcmParserV3 : public EcmParser {
|
||||
std::string ecm_serialized_payload() const override;
|
||||
std::string signature() const override;
|
||||
|
||||
bool is_entitlement_rotation_enabled() const override;
|
||||
uint32_t entitlement_period_index() const override;
|
||||
uint32_t entitlement_rotation_window_left() const override;
|
||||
|
||||
private:
|
||||
// Constructs an EcmParserV3 using |ecm|.
|
||||
EcmParserV3(video_widevine::SignedEcmPayload signed_ecm_payload,
|
||||
|
||||
@@ -118,6 +118,8 @@ class OEMCryptoInterface {
|
||||
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
|
||||
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session);
|
||||
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session);
|
||||
virtual uint32_t OEMCrypto_APIVersion() const;
|
||||
|
||||
OEMCryptoInterface(const OEMCryptoInterface&) = delete;
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "cas_license.h"
|
||||
#include "cas_media_id.h"
|
||||
#include "cas_status.h"
|
||||
#include "cas_types.h"
|
||||
#include "crypto_session.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "emm_parser.h"
|
||||
#include "file_store.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "timer.h"
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
@@ -36,6 +40,9 @@ class WidevineCas : public wvutil::TimerHandler {
|
||||
// Close a previously opened session.
|
||||
virtual CasStatus closeSession(WvCasSessionId sessionId);
|
||||
|
||||
// Process an EMM which may contain fingerprinting and service blocking info.
|
||||
virtual CasStatus processEmm(const CasEmm& emm);
|
||||
|
||||
// Process an ECM from the ECM stream for this session’s elementary
|
||||
// stream.
|
||||
virtual CasStatus processEcm(WvCasSessionId sessionId, const CasEcm& ecm);
|
||||
@@ -74,6 +81,16 @@ class WidevineCas : public wvutil::TimerHandler {
|
||||
virtual CasStatus handleEntitlementRenewalResponse(
|
||||
const std::string& response, std::string& license_id);
|
||||
|
||||
// Generates an entitlement license request in a new crypto session, and send
|
||||
// the license request as an event to the app.
|
||||
virtual CasStatus generateEntitlementPeriodUpdateRequest(
|
||||
const std::string& init_data);
|
||||
|
||||
// Processes the license |response| to switch the current license to this
|
||||
// new one.
|
||||
virtual CasStatus handleEntitlementPeriodUpdateResponse(
|
||||
const std::string& response, std::string& license_id);
|
||||
|
||||
// Returns true if the device has been provisioned with a device certificate.
|
||||
virtual bool is_provisioned() const;
|
||||
|
||||
@@ -97,7 +114,8 @@ class WidevineCas : public wvutil::TimerHandler {
|
||||
// Remove the license file given the filename user provides.
|
||||
virtual CasStatus RemoveLicense(const std::string& file_name);
|
||||
|
||||
// Record the license id that user provides.
|
||||
// Record the license id that user provides. This license id will be used to
|
||||
// select license if multiple licenses exist.
|
||||
virtual CasStatus RecordLicenseId(const std::string& license_id);
|
||||
|
||||
void OnTimerEvent() override;
|
||||
@@ -107,11 +125,30 @@ class WidevineCas : public wvutil::TimerHandler {
|
||||
virtual CasStatus HandleProcessEcm(const WvCasSessionId& sessionId,
|
||||
const CasEcm& ecm);
|
||||
virtual CasStatus HandleDeferredECMs();
|
||||
// Extracts the entitlement rotation period index from ECM if specified, and
|
||||
// store it. The function should be called before any license request and the
|
||||
// extracted index will be included in the license request.
|
||||
virtual void TryExtractEntitlementPeriodIndex(const CasEcm& ecm);
|
||||
// Returns true if an offline license with |filename| is successfully loaded.
|
||||
virtual bool TryReuseStoredLicense(const std::string& filename);
|
||||
// Check if a new license is needed due to entitlement period changes. If so,
|
||||
// it will call generateEntitlementPeriodUpdateRequest().
|
||||
void CheckEntitlementPeriodUpdate(uint32_t period_index,
|
||||
uint32_t window_left);
|
||||
|
||||
virtual std::shared_ptr<CryptoSession> getCryptoSession();
|
||||
virtual std::unique_ptr<CasLicense> getCasLicense();
|
||||
virtual std::unique_ptr<wvutil::FileSystem> getFileSystem();
|
||||
virtual std::shared_ptr<WidevineCasSession> newCasSession();
|
||||
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
|
||||
|
||||
// Creates an EmmParser. Marked as virtual for easier unit test.
|
||||
virtual std::unique_ptr<const EmmParser> getEmmParser(
|
||||
const CasEmm& emm) const;
|
||||
std::vector<uint8_t> GenerateFingerprintingEventMessage(
|
||||
const video_widevine::Fingerprinting& fingerprinting) const;
|
||||
std::vector<uint8_t> GenerateServiceBlockingEventMessage(
|
||||
const video_widevine::ServiceBlocking& service_blocking) const;
|
||||
|
||||
// The CryptoSession will be shared by the all cas sessions. It is also needed
|
||||
// by the cas api to generate EMM requests.
|
||||
@@ -137,15 +174,31 @@ 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
|
||||
// The requested_license_id helps to indicate which license file current
|
||||
// content will use if multiple licenses exist.
|
||||
std::string assigned_license_id_;
|
||||
std::string requested_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_;
|
||||
// Fingerprinting events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::set<CasData> last_fingerprinting_events_;
|
||||
// Service blocking events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::set<CasData> last_service_blocking_events_;
|
||||
// Indicates if |entitlement_period_index_| below is valid or not.
|
||||
bool is_entitlement_rotation_enabled_ = false;
|
||||
// The entitlement period index in the last received ECM.
|
||||
uint32_t entitlement_period_index_;
|
||||
|
||||
// |next_*| used to handle entitlement key rotation. They will be moved to
|
||||
// normal ones once the license switch completed.
|
||||
std::shared_ptr<CryptoSession> next_crypto_session_;
|
||||
std::unique_ptr<CasLicense> next_cas_license_;
|
||||
std::unique_ptr<CasMediaId> next_media_id_;
|
||||
}; // namespace wvcas
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -49,6 +49,8 @@ class WidevineCasSession {
|
||||
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
|
||||
CasEventListener* event_listener, uint32_t* session_id);
|
||||
|
||||
CasStatus resetCryptoSession(std::shared_ptr<CryptoSession> crypto_session);
|
||||
|
||||
// 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
|
||||
@@ -63,7 +65,16 @@ class WidevineCasSession {
|
||||
const char* securityLevel();
|
||||
|
||||
// Returns current ecm age restriction value.
|
||||
uint8_t GetEcmAgeRestriction() { return ecm_age_restriction_; }
|
||||
uint8_t GetEcmAgeRestriction() const { return ecm_age_restriction_; }
|
||||
// Returns the entitlement period index specified in the last received ECM.
|
||||
uint32_t GetEntitlementPeriodIndex() const {
|
||||
return entitlement_period_index_;
|
||||
}
|
||||
// Returns the entitlement rotation window left value specified in the last
|
||||
// received ECM.
|
||||
uint32_t GetEntitlementRotationWindowLeft() const {
|
||||
return entitlement_rotation_window_left_;
|
||||
}
|
||||
|
||||
WidevineCasSession(const WidevineCasSession&) = delete;
|
||||
WidevineCasSession& operator=(const WidevineCasSession&) = delete;
|
||||
@@ -74,7 +85,7 @@ class WidevineCasSession {
|
||||
|
||||
CasKeySlotData keys_; // Odd and even key slots.
|
||||
std::string entitlement_key_id_;
|
||||
std::mutex lock_;
|
||||
std::mutex crypto_lock_;
|
||||
CasEcm current_ecm_;
|
||||
uint8_t ecm_age_restriction_ = 0;
|
||||
std::shared_ptr<CryptoSession> crypto_session_;
|
||||
@@ -87,6 +98,10 @@ class WidevineCasSession {
|
||||
// Service blocking events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::vector<uint8_t> last_service_blocking_message_;
|
||||
// The entitlement period index in the last received ECM.
|
||||
uint32_t entitlement_period_index_;
|
||||
// The entitlement rotation window left in the last received ECM.
|
||||
uint32_t entitlement_rotation_window_left_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -34,6 +34,8 @@ class WidevineCasSessionMap {
|
||||
CasSessionPtr GetSession(WvCasSessionId cas_session_id) const;
|
||||
// Remove an entry in the map.
|
||||
void RemoveSession(WvCasSessionId cas_session_id);
|
||||
// Retrieves all the session ids.
|
||||
std::vector<CasSessionPtr> GetAllSessions() const;
|
||||
|
||||
// Returns a reference to the map.
|
||||
static WidevineCasSessionMap& instance();
|
||||
|
||||
@@ -105,9 +105,11 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
||||
CasStatus HandleSetParentalControlAge(const CasData& data);
|
||||
CasStatus HandleLicenseRemoval(const CasData& license_id);
|
||||
CasStatus HandleAssignLicenseID(const CasData& license_id);
|
||||
CasStatus HandlePluginVersionQuery();
|
||||
CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response);
|
||||
|
||||
// Returns true if the device has been provisioned with a device certificate.
|
||||
bool is_provisioned() const;
|
||||
|
||||
// Event listener implementation
|
||||
void OnSessionRenewalNeeded() override;
|
||||
void OnSessionKeysChange(const KeyStatusMap& keys_status,
|
||||
@@ -123,6 +125,10 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
||||
void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId,
|
||||
const CasData& service_blocking) override;
|
||||
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
|
||||
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
|
||||
void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) override;
|
||||
|
||||
// Choose to use |callback_| or |callback_ext_| to send back information.
|
||||
// |sessionId| is ignored if |callback_ext_| is null,
|
||||
@@ -138,7 +144,8 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
||||
// Otherwise, first CA descriptor available to the plugin
|
||||
// is used to build a PSSH, and others are discarded.
|
||||
bool is_emm_request_sent_ = false;
|
||||
std::string provision_data_;
|
||||
// This is always the serialized PSSH data.
|
||||
std::string init_data_;
|
||||
std::unique_ptr<WidevineCas> widevine_cas_api_;
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ constexpr char kKeyModelName[] = "model_name";
|
||||
constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
constexpr char kKeyDeviceName[] = "device_name";
|
||||
constexpr char kKeyProductName[] = "product_name";
|
||||
constexpr char kKeyWvCasVersion[] = "widevine_cdm_version";
|
||||
|
||||
// TODO(jfore): These variables are currently unused and are flagged as build
|
||||
// errors in android. These values will be used in a future cl.
|
||||
@@ -166,8 +167,6 @@ bool Hash(const std::string& data, std::string* hash) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CasStatus GenerateLicenseFile(
|
||||
const std::string& emm_request, const std::string& emm_response,
|
||||
const std::string& renewal_request, const std::string& renewal_response,
|
||||
@@ -199,6 +198,8 @@ CasStatus GenerateLicenseFile(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session,
|
||||
CasEventListener* listener) {
|
||||
if (!crypto_session) {
|
||||
@@ -360,6 +361,11 @@ CasStatus CasLicense::GenerateEntitlementRequest(
|
||||
client_info->set_name(kKeyDeviceName);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetWvCasPluginVersion(value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kKeyWvCasVersion);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
ClientIdentification_ClientCapabilities* client_capabilities =
|
||||
client_id->mutable_client_capabilities();
|
||||
@@ -433,9 +439,8 @@ CasStatus CasLicense::GenerateEntitlementRequest(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus CasLicense::HandleStoredLicense(
|
||||
const std::string& wrapped_rsa_key, const std::string& license_file,
|
||||
const std::string* content_id_filter) {
|
||||
CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
|
||||
const std::string& license_file) {
|
||||
HashedFile hash_file;
|
||||
if (!hash_file.ParseFromString(license_file)) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
@@ -502,7 +507,7 @@ CasStatus CasLicense::HandleStoredLicense(
|
||||
|
||||
status = InstallLicense(signed_message.session_key(), signed_message.msg(),
|
||||
signed_message.oemcrypto_core_message(),
|
||||
signed_message.signature(), content_id_filter);
|
||||
signed_message.signature());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -524,9 +529,57 @@ CasStatus CasLicense::HandleStoredLicense(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
const std::string& license_file, uint32_t& entitlement_period_index) {
|
||||
HashedFile hash_file;
|
||||
if (!hash_file.ParseFromString(license_file)) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"unable to parse license file");
|
||||
}
|
||||
std::string file_hash;
|
||||
if (!Hash(hash_file.file(), &file_hash)) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"generating file hash fails");
|
||||
}
|
||||
if (hash_file.hash() != file_hash) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"corrupt license file data");
|
||||
}
|
||||
File file;
|
||||
if (!file.ParseFromString(hash_file.file())) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"unable to parse the file data");
|
||||
}
|
||||
if (file.type() != File::LICENSE) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"invalid file type");
|
||||
}
|
||||
// Get PSSH from license request.
|
||||
LicenseRequest license_request;
|
||||
if (!license_request.ParseFromString(file.license().license_request())) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
||||
"invalid license request");
|
||||
}
|
||||
if (license_request.content_id().cenc_id_deprecated().pssh_size() == 0) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError, "no pssh");
|
||||
}
|
||||
// Only one PSSH should exist in the request.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
if (!pssh.ParseFromString(
|
||||
license_request.content_id().cenc_id_deprecated().pssh(0))) {
|
||||
return CasStatus(CasStatusCode::kLicenseFileParseError, "invalid pssh");
|
||||
}
|
||||
|
||||
if (!pssh.has_entitlement_period_index()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"no entitlement period index");
|
||||
}
|
||||
entitlement_period_index = pssh.entitlement_period_index();
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus CasLicense::HandleEntitlementResponse(
|
||||
const std::string& entitlement_response,
|
||||
const std::string* content_id_filter, std::string* device_file) {
|
||||
const std::string& entitlement_response, std::string* device_file) {
|
||||
video_widevine::SignedMessage signed_message;
|
||||
if (!signed_message.ParseFromString(entitlement_response)) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
@@ -549,10 +602,9 @@ 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(), content_id_filter);
|
||||
CasStatus status = InstallLicense(
|
||||
signed_message.session_key(), signed_message.msg(),
|
||||
signed_message.oemcrypto_core_message(), signed_message.signature());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -680,8 +732,7 @@ 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* /*content_id_filter*/) {
|
||||
const std::string& signature) {
|
||||
video_widevine::License license;
|
||||
if (!license.ParseFromString(serialized_license)) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
@@ -728,7 +779,6 @@ CasStatus CasLicense::InstallLicense(const std::string& session_key,
|
||||
mac_key_str.resize(2 * kMacKeySizeBytes);
|
||||
}
|
||||
|
||||
// TODO: apply content_id_filter
|
||||
status =
|
||||
crypto_session_->LoadLicense(serialized_license, core_message, signature);
|
||||
if (!status.ok()) {
|
||||
@@ -1019,4 +1069,23 @@ void CasLicense::OnSessionServiceBlockingUpdated(
|
||||
}
|
||||
}
|
||||
|
||||
void CasLicense::OnFingerprintingUpdated(const CasData& fingerprinting) {
|
||||
if (event_listener_ != nullptr) {
|
||||
event_listener_->OnFingerprintingUpdated(fingerprinting);
|
||||
}
|
||||
}
|
||||
|
||||
void CasLicense::OnServiceBlockingUpdated(const CasData& fingerprinting) {
|
||||
if (event_listener_ != nullptr) {
|
||||
event_listener_->OnServiceBlockingUpdated(fingerprinting);
|
||||
}
|
||||
}
|
||||
|
||||
void CasLicense::OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) {
|
||||
if (event_listener_ != nullptr) {
|
||||
event_listener_->OnEntitlementPeriodUpdateNeeded(signed_license_request);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -16,18 +16,6 @@ static const uint32_t kExpectedOEMCryptoVersion = 16;
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
|
||||
// Find the offset of the |field| within |message|. Notice that if |field| is
|
||||
// empty, 0 will be returned as |pos|.
|
||||
size_t GetOffset(const std::string& message, const std::string& field) {
|
||||
size_t pos = message.find(field);
|
||||
if (pos == std::string::npos) {
|
||||
LOGE("GetOffset : Cannot find offset for %s", field.c_str());
|
||||
pos = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
OEMCryptoCipherMode CipherModeFromKeyData(CryptoMode key_cipher) {
|
||||
switch (key_cipher) {
|
||||
case CryptoMode::kAesCBC:
|
||||
@@ -342,6 +330,14 @@ OEMCryptoResult CryptoInterface::OEMCrypto_RemoveEntitledKeySession(
|
||||
});
|
||||
}
|
||||
|
||||
OEMCryptoResult CryptoInterface::OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid) {
|
||||
return lock_->WithOecSessionLock("ReassociateEntitledKeySession", [&] {
|
||||
return oemcrypto_interface_->OEMCrypto_ReassociateEntitledKeySession(
|
||||
key_sid, oec_sid);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t CryptoInterface::OEMCrypto_APIVersion() {
|
||||
return lock_->WithOecReadLock("APIVersion", [&] {
|
||||
return oemcrypto_interface_->OEMCrypto_APIVersion();
|
||||
@@ -1043,6 +1039,24 @@ CasStatus CryptoSession::RemoveEntitledKeySession(
|
||||
return CasStatus::OkStatus();
|
||||
}
|
||||
|
||||
CasStatus CryptoSession::ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id) {
|
||||
if (!crypto_interface_) {
|
||||
return CasStatus(CasStatusCode::kCryptoSessionError,
|
||||
"missing crypto interface");
|
||||
}
|
||||
|
||||
OEMCryptoResult result =
|
||||
crypto_interface_->OEMCrypto_ReassociateEntitledKeySession(
|
||||
entitled_key_session_id, session_);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
std::ostringstream err_string;
|
||||
err_string << "OEMCrypto_ReassociateEntitledKeySession returned " << result;
|
||||
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
|
||||
}
|
||||
return CasStatus::OkStatus();
|
||||
}
|
||||
|
||||
CasStatus CryptoSession::APIVersion(uint32_t* api_version) {
|
||||
if (!crypto_interface_) {
|
||||
return CasStatus(CasStatusCode::kCryptoSessionError,
|
||||
|
||||
@@ -189,6 +189,7 @@ 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();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
@@ -205,4 +206,16 @@ bool EcmParserV3::set_group_id(const std::string& group_id) {
|
||||
return found;
|
||||
}
|
||||
|
||||
bool EcmParserV3::is_entitlement_rotation_enabled() const {
|
||||
return ecm_payload_.meta_data().has_entitlement_period_index();
|
||||
}
|
||||
|
||||
uint32_t EcmParserV3::entitlement_period_index() const {
|
||||
return ecm_payload_.meta_data().entitlement_period_index();
|
||||
}
|
||||
|
||||
uint32_t EcmParserV3::entitlement_rotation_window_left() const {
|
||||
return ecm_payload_.meta_data().entitlement_rotation_window_left();
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -109,6 +109,8 @@ class OEMCryptoInterface::Impl {
|
||||
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
|
||||
typedef OEMCryptoResult (*RemoveEntitledKeySession_t)(
|
||||
OEMCrypto_SESSION key_session);
|
||||
typedef OEMCryptoResult (*ReassociateEntitledKeySession_t)(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session);
|
||||
typedef uint32_t (*APIVersion_t)();
|
||||
|
||||
Initialize_t Initialize = nullptr;
|
||||
@@ -138,6 +140,7 @@ class OEMCryptoInterface::Impl {
|
||||
SecurityLevel_t SecurityLevel = nullptr;
|
||||
CreateEntitledKeySession_t CreateEntitledKeySession = nullptr;
|
||||
RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr;
|
||||
ReassociateEntitledKeySession_t ReassociateEntitledKeySession = nullptr;
|
||||
APIVersion_t APIVersion = nullptr;
|
||||
|
||||
private:
|
||||
@@ -181,6 +184,7 @@ class OEMCryptoInterface::Impl {
|
||||
LOAD_SYM(CreateEntitledKeySession);
|
||||
LOAD_SYM(RemoveEntitledKeySession);
|
||||
LOAD_SYM(APIVersion);
|
||||
LOAD_SYM(ReassociateEntitledKeySession);
|
||||
|
||||
// Optional methods that may be available.
|
||||
TRY_LOAD_SYM(LoadTestKeybox);
|
||||
@@ -379,6 +383,11 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession(
|
||||
return impl_->RemoveEntitledKeySession(key_session);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) {
|
||||
return impl_->ReassociateEntitledKeySession(key_session, oec_session);
|
||||
}
|
||||
|
||||
uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const {
|
||||
return impl_->APIVersion();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "widevine_cas_api.h"
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_util.h"
|
||||
#include "license_protocol.pb.h"
|
||||
@@ -116,6 +119,23 @@ std::string GenerateGroupLicenseInfo(const std::string& license_id,
|
||||
return message;
|
||||
}
|
||||
|
||||
// Generates a random number between 1 and |range_to|, all inclusive.
|
||||
uint32_t GetRandom(uint32_t range_to) {
|
||||
if (range_to <= 1) {
|
||||
return 1;
|
||||
}
|
||||
constexpr uint32_t max_val = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// Keep searching for a random value in a range divisible by |range_to|.
|
||||
// Worst case we have 1/2 chance to end the loop on each roll.
|
||||
uint32_t generated;
|
||||
do {
|
||||
RAND_bytes(reinterpret_cast<uint8_t*>(&generated), /*len=*/4);
|
||||
} while (generated >= (max_val - (max_val % range_to)));
|
||||
|
||||
return 1 + (generated % range_to);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
@@ -129,6 +149,13 @@ class MediaContext : public CasMediaId {
|
||||
|
||||
const std::string content_id() override { return pssh_.content_id(); }
|
||||
const std::string provider_id() override { return pssh_.provider(); }
|
||||
bool is_entitlement_rotation_enabled() override {
|
||||
return pssh_.has_entitlement_period_index();
|
||||
}
|
||||
uint32_t entitlement_period_index() override {
|
||||
return pssh_.entitlement_period_index();
|
||||
}
|
||||
std::string get_init_data() override { return pssh_.SerializeAsString(); }
|
||||
|
||||
CasStatus initialize(const std::string& init_data) override {
|
||||
if (!pssh_.ParseFromString(init_data)) {
|
||||
@@ -162,6 +189,10 @@ std::shared_ptr<WidevineCasSession> WidevineCas::newCasSession() {
|
||||
return std::make_shared<WidevineCasSession>();
|
||||
}
|
||||
|
||||
std::unique_ptr<EcmParser> WidevineCas::getEcmParser(const CasEcm& ecm) const {
|
||||
return EcmParser::Create(ecm);
|
||||
}
|
||||
|
||||
void WidevineCas::OnTimerEvent() {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (cas_license_.get() != nullptr) {
|
||||
@@ -250,6 +281,60 @@ CasStatus WidevineCas::closeSession(WvCasSessionId sessionId) {
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::processEmm(const CasEmm& emm) {
|
||||
LOGI("WidevineCas::processEmm.");
|
||||
std::unique_ptr<const EmmParser> emm_parser = getEmmParser(emm);
|
||||
if (emm_parser == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "Unable to parse emm");
|
||||
}
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGW("processEmm: Event listener is not initialized.");
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
// TODO(b/): Verify signature.
|
||||
// TODO(b/): Update EMM timer.
|
||||
|
||||
const video_widevine::EmmPayload& emm_payload = emm_parser->emm_payload();
|
||||
// Process fingerprinting info.
|
||||
std::set<CasData> current_fingerprinting_events_;
|
||||
for (int i = 0; i < emm_payload.fingerprinting_size(); ++i) {
|
||||
CasData message =
|
||||
GenerateFingerprintingEventMessage(emm_payload.fingerprinting(i));
|
||||
if (message.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (last_fingerprinting_events_.find(message) ==
|
||||
last_fingerprinting_events_.end()) {
|
||||
event_listener_->OnFingerprintingUpdated(message);
|
||||
}
|
||||
current_fingerprinting_events_.insert(message);
|
||||
}
|
||||
last_fingerprinting_events_.clear();
|
||||
last_fingerprinting_events_.insert(current_fingerprinting_events_.begin(),
|
||||
current_fingerprinting_events_.end());
|
||||
|
||||
// Process service blocking info.
|
||||
std::set<CasData> current_service_blocking_events_;
|
||||
for (int i = 0; i < emm_payload.service_blocking_size(); ++i) {
|
||||
CasData message =
|
||||
GenerateServiceBlockingEventMessage(emm_payload.service_blocking(i));
|
||||
if (message.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (last_service_blocking_events_.find(message) ==
|
||||
last_service_blocking_events_.end()) {
|
||||
event_listener_->OnServiceBlockingUpdated(message);
|
||||
}
|
||||
current_service_blocking_events_.insert(message);
|
||||
}
|
||||
last_service_blocking_events_.clear();
|
||||
last_service_blocking_events_.insert(current_service_blocking_events_.begin(),
|
||||
current_service_blocking_events_.end());
|
||||
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
// TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in
|
||||
// another cl.
|
||||
CasStatus WidevineCas::processEcm(WvCasSessionId sessionId, const CasEcm& ecm) {
|
||||
@@ -257,6 +342,10 @@ CasStatus WidevineCas::processEcm(WvCasSessionId sessionId, const CasEcm& ecm) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
// If we don't have a license yet, save the ecm and session id.
|
||||
if (!has_license_) {
|
||||
// In the case of entitlement key rotation enabled, the caller is expected
|
||||
// to call processEcm first (before processPrivateData), so we know which
|
||||
// entitlement period index to request when requesting license.
|
||||
TryExtractEntitlementPeriodIndex(ecm);
|
||||
deferred_ecms_.emplace(sessionId, ecm);
|
||||
return CasStatusCode::kDeferedEcmProcessing;
|
||||
}
|
||||
@@ -284,6 +373,11 @@ CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId,
|
||||
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
|
||||
}
|
||||
|
||||
if (media_id_ != nullptr && media_id_->is_entitlement_rotation_enabled()) {
|
||||
CheckEntitlementPeriodUpdate(session->GetEntitlementPeriodIndex(),
|
||||
session->GetEntitlementRotationWindowLeft());
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
cas_license_->BeginDecryption();
|
||||
}
|
||||
@@ -344,49 +438,44 @@ CasStatus WidevineCas::generateEntitlementRequest(
|
||||
}
|
||||
|
||||
std::string filename;
|
||||
// Backward compatible. If the license_filename is unassigned by app, plugin
|
||||
// Backward compatible. If the license_filename is unrequested by app, plugin
|
||||
// will directly use the single_content_license named "content_id +
|
||||
// provider_id" by default.
|
||||
if (assigned_license_id_.empty()) {
|
||||
if (requested_license_id_.empty()) {
|
||||
filename = GenerateLicenseFilename(media_id_->content_id(),
|
||||
media_id_->provider_id());
|
||||
} else {
|
||||
filename = assigned_license_id_ + kLicenseFileNameSuffix;
|
||||
filename = requested_license_id_ + kLicenseFileNameSuffix;
|
||||
// Clean up the assigned_license_filename for next round use.
|
||||
assigned_license_id_.clear();
|
||||
requested_license_id_.clear();
|
||||
}
|
||||
|
||||
std::string license_file;
|
||||
if (ReadFileFromStorage(*file_system_, filename, &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.
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (cas_license_->IsExpired()) {
|
||||
if (!RemoveFile(*file_system_, filename)) {
|
||||
return CasStatus(CasStatusCode::kInvalidLicenseFile,
|
||||
"unable to remove expired license file from disk");
|
||||
}
|
||||
LOGI("Remove expired license file from disk successfully.");
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"license is expired, unable to process emm");
|
||||
// An offline license file is successfully loaded.
|
||||
if (TryReuseStoredLicense(filename)) {
|
||||
// If license file is expired, don't proceed the request. Also
|
||||
// delete the stored license file.
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (cas_license_->IsExpired()) {
|
||||
if (!RemoveFile(*file_system_, filename)) {
|
||||
return CasStatus(CasStatusCode::kInvalidLicenseFile,
|
||||
"unable to remove expired license file from disk");
|
||||
}
|
||||
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();
|
||||
LOGI("Remove expired license file from disk successfully.");
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"license is expired, unable to process emm");
|
||||
}
|
||||
LOGI("Fallthru");
|
||||
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();
|
||||
}
|
||||
|
||||
if (entitlement_request == nullptr) {
|
||||
@@ -412,8 +501,8 @@ CasStatus WidevineCas::handleEntitlementResponse(
|
||||
|
||||
std::string device_file;
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
CasStatus status = cas_license_->HandleEntitlementResponse(
|
||||
response, /*content_id_filter=*/nullptr, &device_file);
|
||||
CasStatus status =
|
||||
cas_license_->HandleEntitlementResponse(response, &device_file);
|
||||
if (status.ok()) {
|
||||
// A license has been successfully loaded. Load any ecms that may have been
|
||||
// deferred waiting for the license.
|
||||
@@ -493,6 +582,96 @@ CasStatus WidevineCas::handleEntitlementRenewalResponse(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest(
|
||||
const std::string& init_data) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
next_media_id_ = CasMediaId::create();
|
||||
CasStatus status = next_media_id_->initialize(init_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Setup a new OEMCrypto session.
|
||||
next_crypto_session_ = getCryptoSession();
|
||||
status = next_crypto_session_->initialize();
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas new oemcrypto session failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
// Setup a new CasLicense.
|
||||
next_cas_license_ = getCasLicense();
|
||||
status = next_cas_license_->initialize(next_crypto_session_, event_listener_);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas new license initialize failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
|
||||
std::string entitlement_request;
|
||||
status = next_cas_license_->GenerateEntitlementRequest(
|
||||
init_data, device_certificate_, wrapped_rsa_key_, license_type_,
|
||||
&entitlement_request);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas generate entitlement request failed: %d",
|
||||
status.status_code());
|
||||
return status;
|
||||
}
|
||||
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGE("No event listener");
|
||||
return CasStatus(CasStatusCode::kUnknownError, "No event listener");
|
||||
}
|
||||
event_listener_->OnEntitlementPeriodUpdateNeeded(entitlement_request);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::handleEntitlementPeriodUpdateResponse(
|
||||
const std::string& response, std::string& license_id) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (next_media_id_ == nullptr || next_crypto_session_ == nullptr ||
|
||||
next_cas_license_ == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"Must generate entitlement switch request first.");
|
||||
}
|
||||
// Install the new license.
|
||||
std::string device_file;
|
||||
CasStatus status =
|
||||
next_cas_license_->HandleEntitlementResponse(response, &device_file);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas install new license failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
// License has been successfully installed. Switch to use it across all
|
||||
// sessions.
|
||||
for (const auto& session :
|
||||
WidevineCasSessionMap::instance().GetAllSessions()) {
|
||||
status = session->resetCryptoSession(next_crypto_session_);
|
||||
if (!status.ok()) {
|
||||
// Some of the sessions may have already been reassociated (unlikely to
|
||||
// happen). Here we continue process ignoring the errors. Some sessions
|
||||
// will become unusable.
|
||||
LOGE("resetCryptoSession failed, error %d: %s", status.status_code(),
|
||||
status.error_string().c_str());
|
||||
}
|
||||
}
|
||||
// Close the current OEMCrypto session.
|
||||
crypto_session_->close();
|
||||
// Apply the new crypto session and cas license.
|
||||
crypto_session_ = std::move(next_crypto_session_);
|
||||
cas_license_ = std::move(next_cas_license_);
|
||||
media_id_ = std::move(next_media_id_);
|
||||
|
||||
// Store offline license.
|
||||
if (!device_file.empty()) {
|
||||
std::string filename = GenerateLicenseFilename(media_id_->content_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));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -540,7 +719,13 @@ CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data,
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_provider(descriptor.provider());
|
||||
pssh.set_content_id(descriptor.content_id());
|
||||
for (int i = 0; i < descriptor.group_ids_size(); ++i) {
|
||||
pssh.add_group_ids(descriptor.group_ids(i));
|
||||
}
|
||||
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
if (is_entitlement_rotation_enabled_) {
|
||||
pssh.set_entitlement_period_index(entitlement_period_index_);
|
||||
}
|
||||
pssh.SerializeToString(init_data);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
@@ -578,9 +763,162 @@ CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) {
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::RecordLicenseId(const std::string& license_id) {
|
||||
assigned_license_id_ = license_id;
|
||||
LOGI("License id selected is: %s", assigned_license_id_.c_str());
|
||||
requested_license_id_ = license_id;
|
||||
LOGI("License id selected is: %s", requested_license_id_.c_str());
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WidevineCas::GenerateFingerprintingEventMessage(
|
||||
const video_widevine::Fingerprinting& fingerprinting) const {
|
||||
std::vector<uint8_t> message;
|
||||
for (int i = 0; i < fingerprinting.channels_size(); ++i) {
|
||||
const std::string& channel = fingerprinting.channels(i);
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CHANNEL));
|
||||
message.push_back((channel.size() >> 8) & 0xff);
|
||||
message.push_back(channel.size() & 0xff);
|
||||
message.insert(message.end(), channel.begin(), channel.end());
|
||||
}
|
||||
|
||||
if (fingerprinting.has_control()) {
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CONTROL));
|
||||
const std::string& control = fingerprinting.control();
|
||||
message.push_back((control.size() >> 8) & 0xff);
|
||||
message.push_back(control.size() & 0xff);
|
||||
message.insert(message.end(), control.begin(), control.end());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WidevineCas::GenerateServiceBlockingEventMessage(
|
||||
const video_widevine::ServiceBlocking& service_blocking) const {
|
||||
std::vector<uint8_t> message;
|
||||
// Process service blocking channels.
|
||||
for (int i = 0; i < service_blocking.channels_size(); ++i) {
|
||||
const std::string& channel = service_blocking.channels(i);
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_CHANNEL));
|
||||
message.push_back((channel.size() >> 8) & 0xff);
|
||||
message.push_back(channel.size() & 0xff);
|
||||
message.insert(message.end(), channel.begin(), channel.end());
|
||||
}
|
||||
|
||||
// Process service blocking device_groups.
|
||||
for (int i = 0; i < service_blocking.device_groups_size(); ++i) {
|
||||
const std::string& device_group = service_blocking.device_groups(i);
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_DEVICE_GROUP));
|
||||
message.push_back((device_group.size() >> 8) & 0xff);
|
||||
message.push_back(device_group.size() & 0xff);
|
||||
message.insert(message.end(), device_group.begin(), device_group.end());
|
||||
}
|
||||
|
||||
// Process service blocking start_time_sec.
|
||||
if (service_blocking.has_start_time_sec()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_START_TIME_SECONDS));
|
||||
// Timestamp is always 8 bytes (64 bits).
|
||||
message.push_back(0);
|
||||
message.push_back(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
message.push_back((service_blocking.start_time_sec() >> (8 * (7 - i))) &
|
||||
0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// Process service blocking end_time_sec.
|
||||
if (service_blocking.has_end_time_sec()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_END_TIME_SECONDS));
|
||||
// Timestamp is always 8 bytes (64 bits).
|
||||
message.push_back(0);
|
||||
message.push_back(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
message.push_back((service_blocking.end_time_sec() >> (8 * (7 - i))) &
|
||||
0xff);
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::unique_ptr<const EmmParser> WidevineCas::getEmmParser(
|
||||
const CasEmm& emm) const {
|
||||
return EmmParser::Create(emm);
|
||||
}
|
||||
|
||||
void WidevineCas::TryExtractEntitlementPeriodIndex(const CasEcm& ecm) {
|
||||
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
|
||||
if (ecm_parser == nullptr) {
|
||||
LOGE("ECM parser failed for extracting entitlement period index");
|
||||
return;
|
||||
}
|
||||
if (ecm_parser->is_entitlement_rotation_enabled()) {
|
||||
is_entitlement_rotation_enabled_ = true;
|
||||
entitlement_period_index_ = ecm_parser->entitlement_period_index();
|
||||
LOGI("Entitlement key rotation enabled. Current index: %d",
|
||||
entitlement_period_index_);
|
||||
}
|
||||
}
|
||||
|
||||
bool WidevineCas::TryReuseStoredLicense(const std::string& filename) {
|
||||
// Read the file with |filename| from the file system.
|
||||
std::string license_file;
|
||||
if (!ReadFileFromStorage(*file_system_, filename, &license_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If entitlement rotation is enabled, check if the entitlement period in the
|
||||
// license is outdated.
|
||||
if (media_id_->is_entitlement_rotation_enabled()) {
|
||||
uint32_t stored_index;
|
||||
CasStatus status = CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
license_file, stored_index);
|
||||
if (!status.ok()) {
|
||||
LOGW(
|
||||
"Failed to retrieve entitlement period index from stored license. "
|
||||
"code: %d, message: %s",
|
||||
status.status_code(), status.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (media_id_->entitlement_period_index() != stored_index) {
|
||||
LOGI("Stored license has mismatch entitlement period index.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the stored license to the session.
|
||||
CasStatus status =
|
||||
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
|
||||
if (!status.ok()) {
|
||||
LOGW("Failed to load stored license. code: %d, message: %s",
|
||||
status.status_code(), status.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WidevineCas::CheckEntitlementPeriodUpdate(uint32_t period_index,
|
||||
uint32_t window_left) {
|
||||
if (period_index == media_id_->entitlement_period_index()) {
|
||||
return;
|
||||
}
|
||||
// If the index changed unexpectedly, we request a new license immediately. If
|
||||
// it is increased by 1, we decide if a new license should be generated based
|
||||
// on |window_left|.
|
||||
if (period_index != media_id_->entitlement_period_index() + 1 ||
|
||||
GetRandom(window_left) == 1) {
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
if (!pssh.ParseFromString(media_id_->get_init_data())) {
|
||||
LOGE("Cannot parse init data");
|
||||
return;
|
||||
}
|
||||
pssh.set_entitlement_period_index(period_index);
|
||||
generateEntitlementPeriodUpdateRequest(pssh.SerializeAsString());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -30,6 +30,7 @@ WidevineCasSession::~WidevineCasSession() {
|
||||
CasStatus WidevineCasSession::initialize(
|
||||
std::shared_ptr<CryptoSession> crypto_session,
|
||||
CasEventListener* event_listener, uint32_t* session_id) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (crypto_session == nullptr || session_id == nullptr) {
|
||||
LOGE("WidevineCasSession::initialize: missing input parameters");
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
@@ -42,9 +43,21 @@ CasStatus WidevineCasSession::initialize(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::resetCryptoSession(
|
||||
std::shared_ptr<CryptoSession> crypto_session) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (crypto_session == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"Can not reset crypto session to null");
|
||||
}
|
||||
crypto_session_ = std::move(crypto_session);
|
||||
return crypto_session_->ReassociateEntitledKeySession(key_session_id_);
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
uint8_t parental_control_age,
|
||||
const std::string& license_group_id) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (ecm != current_ecm_) {
|
||||
LOGD("WidevineCasSession::processEcm: received new ecm");
|
||||
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
|
||||
@@ -67,7 +80,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
std::vector<uint8_t> message;
|
||||
if (!ecm_parser->fingerprinting().control().empty()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
SessionFingerprintingFieldType::FINGERPRINTING_CONTROL));
|
||||
SessionFingerprintingFieldType::SESSION_FINGERPRINTING_CONTROL));
|
||||
const std::string control = ecm_parser->fingerprinting().control();
|
||||
message.push_back((control.size() >> 8) & 0xff);
|
||||
message.push_back(control.size() & 0xff);
|
||||
@@ -86,8 +99,9 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
message.clear();
|
||||
for (int i = 0; i < ecm_parser->service_blocking().device_groups_size();
|
||||
++i) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
SessionServiceBlockingFieldType::SERVICE_BLOCKING_DEVICE_GROUP));
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(SessionServiceBlockingFieldType::
|
||||
SESSION_SERVICE_BLOCKING_DEVICE_GROUP));
|
||||
const std::string device_group =
|
||||
ecm_parser->service_blocking().device_groups(i);
|
||||
message.push_back((device_group.size() >> 8) & 0xff);
|
||||
@@ -104,10 +118,12 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
}
|
||||
}
|
||||
|
||||
entitlement_period_index_ = ecm_parser->entitlement_period_index();
|
||||
entitlement_rotation_window_left_ =
|
||||
ecm_parser->entitlement_rotation_window_left();
|
||||
|
||||
bool load_even = false;
|
||||
bool load_odd = false;
|
||||
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
KeySlotId keyslot_id = KeySlotId::kEvenKeySlot;
|
||||
// Temporary key slots to only have successfully loaded keys in |keys_|.
|
||||
CasKeySlotData keys;
|
||||
|
||||
@@ -34,4 +34,13 @@ void WidevineCasSessionMap::RemoveSession(WvCasSessionId cas_session_id) {
|
||||
map_.erase(cas_session_id);
|
||||
}
|
||||
|
||||
std::vector<CasSessionPtr> WidevineCasSessionMap::GetAllSessions() const {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
std::vector<CasSessionPtr> sessions;
|
||||
for (const auto& session : map_) {
|
||||
sessions.push_back(session.second);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_properties.h"
|
||||
#include "cas_status.h"
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
@@ -72,6 +73,14 @@ status_t WidevineCasPlugin::initialize() {
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
|
||||
std::string version;
|
||||
if (Properties::GetWvCasPluginVersion(version)) {
|
||||
LOGI("Widevine CAS plugin version: %s", version.c_str());
|
||||
} else {
|
||||
LOGW("Failed to get Widevine CAS plugin version.");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -85,7 +94,6 @@ status_t WidevineCasPlugin::setStatusCallback(
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Will be called directly by player starting Android Q, see b/119039060.
|
||||
status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
|
||||
// Can get PSSH from multiple streams and from provision call.
|
||||
// Only need to request a license once.
|
||||
@@ -93,12 +101,12 @@ status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
|
||||
return OK;
|
||||
}
|
||||
CasStatus status =
|
||||
widevine_cas_api_->ProcessCAPrivateData(privateData, &provision_data_);
|
||||
widevine_cas_api_->ProcessCAPrivateData(privateData, &init_data_);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
if (widevine_cas_api_->is_provisioned()) {
|
||||
return requestLicense(provision_data_);
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -153,12 +161,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_api_->ProcessSessionCAPrivateData(
|
||||
wv_session_id, privateData, &provision_data_);
|
||||
wv_session_id, privateData, &init_data_);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
||||
}
|
||||
if (widevine_cas_api_->is_provisioned()) {
|
||||
return requestLicense(provision_data_);
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -190,9 +198,12 @@ status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::processEmm(const CasEmm& /*emm*/) {
|
||||
// TODO(jfore): This is used for inband emm. Currently unsupported.
|
||||
return INVALID_OPERATION;
|
||||
status_t WidevineCasPlugin::processEmm(const CasEmm& emm) {
|
||||
LOGI("WidevineCasPlugin::processEmm");
|
||||
if (!widevine_cas_api_->processEmm(emm).ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg,
|
||||
@@ -225,15 +236,14 @@ status_t WidevineCasPlugin::sendSessionEvent(const CasSessionId& sessionId,
|
||||
status_t WidevineCasPlugin::provision(const String8& provisionString) {
|
||||
// Store |provisionString| for future use. If |provisionString| is not empty
|
||||
// the value takes priority over data in CA descriptor.
|
||||
provision_data_ =
|
||||
std::string(provisionString.c_str(),
|
||||
provisionString.c_str() + provisionString.length());
|
||||
init_data_ = std::string(provisionString.c_str(),
|
||||
provisionString.c_str() + provisionString.length());
|
||||
|
||||
if (is_provisioned()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
|
||||
nullptr, 0, nullptr);
|
||||
if (!provision_data_.empty()) {
|
||||
return requestLicense(provision_data_);
|
||||
if (!init_data_.empty()) {
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
@@ -307,6 +317,10 @@ CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
|
||||
return HandleLicenseRemoval(eventData);
|
||||
case ASSIGN_LICENSE_ID:
|
||||
return HandleAssignLicenseID(eventData);
|
||||
case QUERY_WV_CAS_PLUGIN_VERSION:
|
||||
return HandlePluginVersionQuery();
|
||||
case LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE:
|
||||
return HandleEntitlementPeriodUpdateResponse(eventData);
|
||||
default:
|
||||
return CasStatusCode::kUnknownEvent;
|
||||
}
|
||||
@@ -328,9 +342,9 @@ CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
|
||||
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
|
||||
nullptr, 0, nullptr);
|
||||
|
||||
if (!provision_data_.empty() && !is_emm_request_sent_) {
|
||||
if (!init_data_.empty() && !is_emm_request_sent_) {
|
||||
LOGD("Making license request with provisioned PSSH");
|
||||
if (requestLicense(provision_data_) != OK) {
|
||||
if (requestLicense(init_data_) != OK) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"failed to generate license request");
|
||||
}
|
||||
@@ -389,6 +403,25 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleEntitlementPeriodUpdateResponse(
|
||||
const CasData& response) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "empty license response");
|
||||
}
|
||||
std::string resp_string(response.begin(), response.end());
|
||||
std::string license_id;
|
||||
CasStatus status = widevine_cas_api_->handleEntitlementPeriodUpdateResponse(
|
||||
resp_string, license_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
CallBack(
|
||||
reinterpret_cast<void*>(app_data_), LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(), nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
|
||||
std::string buffer;
|
||||
CasStatus status = widevine_cas_api_->GetUniqueID(&buffer);
|
||||
@@ -447,6 +480,18 @@ CasStatus WidevineCasPlugin::HandleAssignLicenseID(
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandlePluginVersionQuery() {
|
||||
std::string version;
|
||||
if (!Properties::GetWvCasPluginVersion(version)) {
|
||||
return CasStatus(CasStatusCode::kUnknownError,
|
||||
"unable to get plugin version");
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), WV_CAS_PLUGIN_VERSION,
|
||||
static_cast<int32_t>(0), reinterpret_cast<uint8_t*>(&version[0]),
|
||||
version.size(), /*sessionId=*/nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionRenewalNeeded() {
|
||||
LOGI("OnSessionRenewalNeeded");
|
||||
std::string renewal_request;
|
||||
@@ -535,6 +580,40 @@ void WidevineCasPlugin::OnSessionServiceBlockingUpdated(
|
||||
service_blocking.size(), &android_session_id);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnFingerprintingUpdated(const CasData& fingerprinting) {
|
||||
if (fingerprinting.empty()) {
|
||||
return;
|
||||
}
|
||||
LOGI("OnFingerprintingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), FINGERPRINTING_INFO, /*arg=*/0,
|
||||
const_cast<uint8_t*>(&fingerprinting[0]), fingerprinting.size(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnServiceBlockingUpdated(
|
||||
const CasData& service_blocking) {
|
||||
if (service_blocking.empty()) {
|
||||
return;
|
||||
}
|
||||
LOGI("OnServiceBlockingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), SERVICE_BLOCKING_INFO, /*arg=*/0,
|
||||
const_cast<uint8_t*>(&service_blocking[0]), service_blocking.size(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) {
|
||||
LOGI("OnEntitlementPeriodUpdateNeeded");
|
||||
if (!signed_license_request.empty()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
const_cast<uint8_t*>(
|
||||
reinterpret_cast<const uint8_t*>(&signed_license_request[0])),
|
||||
signed_license_request.size(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg,
|
||||
uint8_t* data, size_t size,
|
||||
const CasSessionId* sessionId) const {
|
||||
|
||||
Reference in New Issue
Block a user