Group license support
This commit is contained in:
@@ -32,6 +32,11 @@ typedef enum {
|
|||||||
LICENSE_CAS_RENEWAL_READY,
|
LICENSE_CAS_RENEWAL_READY,
|
||||||
LICENSE_REMOVAL,
|
LICENSE_REMOVAL,
|
||||||
LICENSE_REMOVED,
|
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
|
// TODO(jfore): Evaluate removing this event in favor of return status codes
|
||||||
// from
|
// from
|
||||||
@@ -87,4 +92,16 @@ typedef enum {
|
|||||||
SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
||||||
} SessionServiceBlockingFieldType;
|
} 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
|
#endif // CAS_EVENTS_H
|
||||||
|
|||||||
@@ -53,15 +53,22 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
|
|||||||
std::string* signed_license_request);
|
std::string* signed_license_request);
|
||||||
|
|
||||||
// Restores a stored license making the keys available for use.
|
// 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,
|
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
|
// Process a server response containing a EMM for use in the processing of
|
||||||
// processing of ECM(s). If |device_file| is not nullptr and the license
|
// ECM(s).
|
||||||
// policy allows a license to be stored |device_file| is populated with the
|
// If |content_id_filter| is not null, only matching entitlement keys (as
|
||||||
// bytes of the license secured for storage.
|
// 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(
|
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
|
// Process a previously stored device |certificate| and make it available
|
||||||
// for use in an EMM request.
|
// 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.
|
// Query the license to see if storage is allowed.
|
||||||
virtual bool CanStoreLicense() const;
|
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.
|
// Policy timer implentation.
|
||||||
void OnTimerEvent() override;
|
void OnTimerEvent() override;
|
||||||
|
|
||||||
@@ -133,7 +156,8 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
|
|||||||
CasStatus InstallLicense(const std::string& session_key,
|
CasStatus InstallLicense(const std::string& session_key,
|
||||||
const std::string& serialized_license,
|
const std::string& serialized_license,
|
||||||
const std::string& core_message,
|
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,
|
CasStatus InstallLicenseRenewal(const std::string& serialized_license,
|
||||||
const std::string& core_message,
|
const std::string& core_message,
|
||||||
const std::string& signature);
|
const std::string& signature);
|
||||||
|
|||||||
@@ -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_
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
#include "OEMCryptoCAS.h"
|
#include "OEMCryptoCAS.h"
|
||||||
#include "cas_status.h"
|
#include "cas_status.h"
|
||||||
#include "cas_types.h"
|
#include "cas_types.h"
|
||||||
#include "crypto_key.h"
|
|
||||||
#include "oemcrypto_interface.h"
|
#include "oemcrypto_interface.h"
|
||||||
#include "rw_lock.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 enc_session_key_length, const uint8_t* mac_key_context,
|
||||||
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||||
size_t enc_key_context_length);
|
size_t enc_key_context_length);
|
||||||
virtual OEMCryptoResult OEMCrypto_LoadKeys(
|
|
||||||
const LoadKeysParams& load_key_params);
|
|
||||||
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
||||||
const uint8_t* message,
|
const uint8_t* message,
|
||||||
size_t message_length,
|
size_t message_length,
|
||||||
@@ -168,10 +165,6 @@ class CryptoInterface {
|
|||||||
OEMCryptoCipherMode cipher_mode);
|
OEMCryptoCipherMode cipher_mode);
|
||||||
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
||||||
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max);
|
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,
|
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||||
size_t* idLength);
|
size_t* idLength);
|
||||||
virtual const char* OEMCrypto_SecurityLevel();
|
virtual const char* OEMCrypto_SecurityLevel();
|
||||||
@@ -267,13 +260,6 @@ class CryptoSession {
|
|||||||
size_t mac_key_context_length,
|
size_t mac_key_context_length,
|
||||||
const uint8_t* enc_key_context,
|
const uint8_t* enc_key_context,
|
||||||
size_t enc_key_context_length);
|
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,
|
virtual CasStatus LoadLicense(const std::string& signed_message,
|
||||||
const std::string& core_message,
|
const std::string& core_message,
|
||||||
const std::string& signature);
|
const std::string& signature);
|
||||||
@@ -292,9 +278,6 @@ class CryptoSession {
|
|||||||
CryptoMode crypto_mode);
|
CryptoMode crypto_mode);
|
||||||
virtual bool GetHdcpCapabilities(HdcpCapability* current,
|
virtual bool GetHdcpCapabilities(HdcpCapability* current,
|
||||||
HdcpCapability* max);
|
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 CasStatus GetDeviceID(std::string* buffer);
|
||||||
virtual const char* SecurityLevel();
|
virtual const char* SecurityLevel();
|
||||||
virtual CasStatus CreateEntitledKeySession(
|
virtual CasStatus CreateEntitledKeySession(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class EcmParser {
|
|||||||
// The EcmParser factory method.
|
// The EcmParser factory method.
|
||||||
// Validates the ecm. If validations is successful returns true and constructs
|
// Validates the ecm. If validations is successful returns true and constructs
|
||||||
// an EcmParser in |parser| using |ecm|.
|
// 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.
|
// Accessor methods.
|
||||||
virtual uint8_t version() const = 0;
|
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> wrapped_key_iv(KeySlotId id) const = 0;
|
||||||
virtual std::vector<uint8_t> content_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 bool has_fingerprinting() const = 0;
|
||||||
virtual video_widevine::Fingerprinting fingerprinting() const = 0;
|
virtual video_widevine::Fingerprinting fingerprinting() const = 0;
|
||||||
virtual bool has_service_blocking() const = 0;
|
virtual bool has_service_blocking() const = 0;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class EcmParserV2 : public EcmParser {
|
|||||||
// successful returns true and constructs an EcmParserV2 in |parser| using
|
// successful returns true and constructs an EcmParserV2 in |parser| using
|
||||||
// |ecm|.
|
// |ecm|.
|
||||||
static bool create(const CasEcm& cas_ecm,
|
static bool create(const CasEcm& cas_ecm,
|
||||||
std::unique_ptr<const EcmParserV2>* parser);
|
std::unique_ptr<EcmParserV2>* parser);
|
||||||
|
|
||||||
// Accessor methods.
|
// Accessor methods.
|
||||||
uint8_t version() const override;
|
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> wrapped_key_iv(KeySlotId id) const override;
|
||||||
std::vector<uint8_t> content_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.
|
// ECM v2 or under does not have these fields.
|
||||||
bool has_fingerprinting() const override { return false; }
|
bool has_fingerprinting() const override { return false; }
|
||||||
video_widevine::Fingerprinting fingerprinting() const override {
|
video_widevine::Fingerprinting fingerprinting() const override {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class EcmParserV3 : public EcmParser {
|
|||||||
// |ecm| must be Widevine ECM v3 (or higher if compatible) without section
|
// |ecm| must be Widevine ECM v3 (or higher if compatible) without section
|
||||||
// header. Validates the ecm. If validations is successful returns an
|
// header. Validates the ecm. If validations is successful returns an
|
||||||
// EcmParserV3, otherwise an nullptr.
|
// 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.
|
// Accessor methods.
|
||||||
uint8_t version() const override;
|
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> wrapped_key_iv(KeySlotId id) const override;
|
||||||
std::vector<uint8_t> content_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;
|
bool has_fingerprinting() const override;
|
||||||
video_widevine::Fingerprinting fingerprinting() const override;
|
video_widevine::Fingerprinting fingerprinting() const override;
|
||||||
bool has_service_blocking() const override;
|
bool has_service_blocking() const override;
|
||||||
@@ -53,6 +55,8 @@ class EcmParserV3 : public EcmParser {
|
|||||||
video_widevine::EcmPayload ecm_payload);
|
video_widevine::EcmPayload ecm_payload);
|
||||||
video_widevine::SignedEcmPayload signed_ecm_payload_;
|
video_widevine::SignedEcmPayload signed_ecm_payload_;
|
||||||
video_widevine::EcmPayload ecm_payload_;
|
video_widevine::EcmPayload ecm_payload_;
|
||||||
|
video_widevine::EcmKeyData even_key_data_;
|
||||||
|
video_widevine::EcmKeyData odd_key_data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace wvcas
|
} // namespace wvcas
|
||||||
|
|||||||
@@ -10,28 +10,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "OEMCryptoCAS.h"
|
#include "OEMCryptoCAS.h"
|
||||||
#include "crypto_key.h"
|
|
||||||
|
|
||||||
namespace wvcas {
|
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
|
// InputStreamParams mirrors the parameters in OEMCrypto API. The
|
||||||
// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble
|
// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble
|
||||||
// takes 11 parameters as of API V15. GoogleMock allows a maximum of 10.
|
// 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 enc_session_key_length, const uint8_t* mac_key_context,
|
||||||
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||||
size_t enc_key_context_length) const;
|
size_t enc_key_context_length) const;
|
||||||
virtual OEMCryptoResult OEMCrypto_LoadKeys(
|
|
||||||
const LoadKeysParams& load_key_params) const;
|
|
||||||
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
||||||
const uint8_t* message,
|
const uint8_t* message,
|
||||||
size_t message_length,
|
size_t message_length,
|
||||||
@@ -130,10 +109,6 @@ class OEMCryptoInterface {
|
|||||||
size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) const;
|
size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) const;
|
||||||
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
||||||
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const;
|
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,
|
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||||
size_t* idLength);
|
size_t* idLength);
|
||||||
virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer,
|
virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer,
|
||||||
|
|||||||
@@ -54,8 +54,15 @@ class WidevineCas : public wvutil::TimerHandler {
|
|||||||
std::string& license_id);
|
std::string& license_id);
|
||||||
|
|
||||||
// Processes the entitlement |response| to a entitlement license request.
|
// Processes the entitlement |response| to a entitlement license request.
|
||||||
virtual CasStatus handleEntitlementResponse(const std::string& response,
|
// |license_id| is the id of the license installed. Can be used to select
|
||||||
std::string& license_id);
|
// 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
|
// Generates an entitlement license request in |entitlement_request| for the
|
||||||
// media described in |init_data|.
|
// media described in |init_data|.
|
||||||
@@ -87,8 +94,11 @@ class WidevineCas : public wvutil::TimerHandler {
|
|||||||
// Set the minimum age required to process ECM.
|
// Set the minimum age required to process ECM.
|
||||||
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
|
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
|
||||||
|
|
||||||
// Remove the in used license. If successful content id is returned.
|
// Remove the license file given the filename user provides.
|
||||||
virtual CasStatus RemoveLicense(const std::string file_name);
|
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;
|
void OnTimerEvent() override;
|
||||||
|
|
||||||
@@ -127,6 +137,15 @@ class WidevineCas : public wvutil::TimerHandler {
|
|||||||
// The age_restriction field in ECM must be greater or equal to
|
// The age_restriction field in ECM must be greater or equal to
|
||||||
// |parental_control_min_age|. Otherwise, ECM will stop being processed.
|
// |parental_control_min_age|. Otherwise, ECM will stop being processed.
|
||||||
uint parental_control_age_ = 0;
|
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
|
||||||
|
|
||||||
} // namespace wvcas
|
} // namespace wvcas
|
||||||
|
|||||||
@@ -49,16 +49,15 @@ class WidevineCasSession {
|
|||||||
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
|
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
|
||||||
CasEventListener* event_listener, uint32_t* session_id);
|
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
|
// Process an ecm and extract the key slot data. Extracted data will be used
|
||||||
// to update |current_ecm_| and |entitlement_key_id_| and |keys_|.
|
// to update |current_ecm_| and |entitlement_key_id_| and |keys_|.
|
||||||
// |parental_control_age| (if non-zero) must be greater or equal to the
|
// |parental_control_age| (if non-zero) must be greater or equal to the
|
||||||
// age_restriction field specified in |ecm|. Otherwise, ECM will not be
|
// age_restriction field specified in |ecm|. Otherwise, ECM will not be
|
||||||
// processed and error will be returned.
|
// 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.
|
// Returns the security level retrieved from OEMCrypto.
|
||||||
const char* securityLevel();
|
const char* securityLevel();
|
||||||
@@ -71,8 +70,7 @@ class WidevineCasSession {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Creates an EcmParser.
|
// Creates an EcmParser.
|
||||||
virtual std::unique_ptr<const EcmParser> getEcmParser(
|
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
|
||||||
const CasEcm& ecm) const;
|
|
||||||
|
|
||||||
CasKeySlotData keys_; // Odd and even key slots.
|
CasKeySlotData keys_; // Odd and even key slots.
|
||||||
std::string entitlement_key_id_;
|
std::string entitlement_key_id_;
|
||||||
|
|||||||
@@ -30,12 +30,9 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
|||||||
// MediaCas platform api documentation.
|
// MediaCas platform api documentation.
|
||||||
WidevineCasPlugin(void* appData, CasPluginCallback callback);
|
WidevineCasPlugin(void* appData, CasPluginCallback callback);
|
||||||
WidevineCasPlugin(void* appData, CasPluginCallbackExt callback);
|
WidevineCasPlugin(void* appData, CasPluginCallbackExt callback);
|
||||||
~WidevineCasPlugin() override {}
|
virtual ~WidevineCasPlugin() override {}
|
||||||
|
|
||||||
virtual status_t initialize();
|
status_t initialize();
|
||||||
|
|
||||||
// Returns true if the device has been provisioned with a device certificate.
|
|
||||||
bool is_provisioned();
|
|
||||||
|
|
||||||
// Provide a callback to report plugin status.
|
// Provide a callback to report plugin status.
|
||||||
status_t setStatusCallback(CasPluginStatusCallback callback) override;
|
status_t setStatusCallback(CasPluginStatusCallback callback) override;
|
||||||
@@ -88,19 +85,28 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
|||||||
WidevineCasPlugin(const WidevineCasPlugin&) = delete;
|
WidevineCasPlugin(const WidevineCasPlugin&) = delete;
|
||||||
WidevineCasPlugin& operator=(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:
|
private:
|
||||||
virtual std::shared_ptr<CryptoSession> getCryptoSession();
|
|
||||||
// |sessionId| is nullptr if the event is not a session event.
|
// |sessionId| is nullptr if the event is not a session event.
|
||||||
virtual CasStatus processEvent(int32_t event, int32_t arg,
|
CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData,
|
||||||
const CasData& eventData,
|
const CasSessionId* sessionId);
|
||||||
const CasSessionId* sessionId);
|
CasStatus HandleIndividualizationResponse(const CasData& response);
|
||||||
virtual CasStatus HandleIndividualizationResponse(const CasData& response);
|
CasStatus HandleEntitlementResponse(const CasData& response);
|
||||||
virtual CasStatus HandleEntitlementResponse(const CasData& response);
|
status_t requestLicense(const std::string& init_data);
|
||||||
virtual status_t requestLicense(const std::string& init_data);
|
CasStatus HandleEntitlementRenewalResponse(const CasData& response);
|
||||||
virtual CasStatus HandleEntitlementRenewalResponse(const CasData& response);
|
CasStatus HandleUniqueIDQuery();
|
||||||
virtual CasStatus HandleUniqueIDQuery();
|
CasStatus HandleSetParentalControlAge(const CasData& data);
|
||||||
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
|
CasStatus HandleLicenseRemoval(const CasData& license_id);
|
||||||
virtual 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
|
// Event listener implementation
|
||||||
void OnSessionRenewalNeeded() override;
|
void OnSessionRenewalNeeded() override;
|
||||||
@@ -120,8 +126,9 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
|||||||
|
|
||||||
// Choose to use |callback_| or |callback_ext_| to send back information.
|
// Choose to use |callback_| or |callback_ext_| to send back information.
|
||||||
// |sessionId| is ignored if |callback_ext_| is null,
|
// |sessionId| is ignored if |callback_ext_| is null,
|
||||||
void CallBack(void* appData, int32_t event, int32_t arg, uint8_t* data,
|
virtual void CallBack(void* appData, int32_t event, int32_t arg,
|
||||||
size_t size, const CasSessionId* sessionId) const;
|
uint8_t* data, size_t size,
|
||||||
|
const CasSessionId* sessionId) const;
|
||||||
|
|
||||||
void* app_data_;
|
void* app_data_;
|
||||||
CasPluginCallback callback_;
|
CasPluginCallback callback_;
|
||||||
@@ -132,7 +139,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
|||||||
// is used to build a PSSH, and others are discarded.
|
// is used to build a PSSH, and others are discarded.
|
||||||
bool is_emm_request_sent_ = false;
|
bool is_emm_request_sent_ = false;
|
||||||
std::string provision_data_;
|
std::string provision_data_;
|
||||||
WidevineCas widevine_cas_;
|
std::unique_ptr<WidevineCas> widevine_cas_api_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace wvcas
|
} // namespace wvcas
|
||||||
|
|||||||
@@ -7,12 +7,13 @@
|
|||||||
#include <openssl/sha.h>
|
#include <openssl/sha.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "cas_properties.h"
|
#include "cas_properties.h"
|
||||||
#include "cas_util.h"
|
#include "cas_util.h"
|
||||||
#include "crypto_key.h"
|
|
||||||
#include "crypto_session.h"
|
#include "crypto_session.h"
|
||||||
#include "device_files.pb.h"
|
#include "device_files.pb.h"
|
||||||
#include "license_protocol.pb.h"
|
#include "license_protocol.pb.h"
|
||||||
@@ -167,51 +168,6 @@ bool Hash(const std::string& data, std::string* hash) {
|
|||||||
|
|
||||||
} // namespace
|
} // 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(
|
CasStatus GenerateLicenseFile(
|
||||||
const std::string& emm_request, const std::string& emm_response,
|
const std::string& emm_request, const std::string& emm_response,
|
||||||
const std::string& renewal_request, const std::string& renewal_response,
|
const std::string& renewal_request, const std::string& renewal_response,
|
||||||
@@ -477,8 +433,9 @@ CasStatus CasLicense::GenerateEntitlementRequest(
|
|||||||
return CasStatusCode::kNoError;
|
return CasStatusCode::kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
|
CasStatus CasLicense::HandleStoredLicense(
|
||||||
const std::string& license_file) {
|
const std::string& wrapped_rsa_key, const std::string& license_file,
|
||||||
|
const std::string* content_id_filter) {
|
||||||
HashedFile hash_file;
|
HashedFile hash_file;
|
||||||
if (!hash_file.ParseFromString(license_file)) {
|
if (!hash_file.ParseFromString(license_file)) {
|
||||||
return CasStatus(CasStatusCode::kLicenseFileParseError,
|
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(),
|
status = InstallLicense(signed_message.session_key(), signed_message.msg(),
|
||||||
signed_message.oemcrypto_core_message(),
|
signed_message.oemcrypto_core_message(),
|
||||||
signed_message.signature());
|
signed_message.signature(), content_id_filter);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -568,7 +525,8 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CasStatus CasLicense::HandleEntitlementResponse(
|
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;
|
video_widevine::SignedMessage signed_message;
|
||||||
if (!signed_message.ParseFromString(entitlement_response)) {
|
if (!signed_message.ParseFromString(entitlement_response)) {
|
||||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||||
@@ -591,9 +549,10 @@ CasStatus CasLicense::HandleEntitlementResponse(
|
|||||||
"no oemcrypto core message present");
|
"no oemcrypto core message present");
|
||||||
}
|
}
|
||||||
|
|
||||||
CasStatus status = InstallLicense(
|
CasStatus status =
|
||||||
signed_message.session_key(), signed_message.msg(),
|
InstallLicense(signed_message.session_key(), signed_message.msg(),
|
||||||
signed_message.oemcrypto_core_message(), signed_message.signature());
|
signed_message.oemcrypto_core_message(),
|
||||||
|
signed_message.signature(), content_id_filter);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -721,7 +680,8 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const {
|
|||||||
CasStatus CasLicense::InstallLicense(const std::string& session_key,
|
CasStatus CasLicense::InstallLicense(const std::string& session_key,
|
||||||
const std::string& serialized_license,
|
const std::string& serialized_license,
|
||||||
const std::string& core_message,
|
const std::string& core_message,
|
||||||
const std::string& signature) {
|
const std::string& signature,
|
||||||
|
const std::string* /*content_id_filter*/) {
|
||||||
video_widevine::License license;
|
video_widevine::License license;
|
||||||
if (!license.ParseFromString(serialized_license)) {
|
if (!license.ParseFromString(serialized_license)) {
|
||||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||||
@@ -768,12 +728,7 @@ CasStatus CasLicense::InstallLicense(const std::string& session_key,
|
|||||||
mac_key_str.resize(2 * kMacKeySizeBytes);
|
mac_key_str.resize(2 * kMacKeySizeBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CryptoKey> key_array = ExtractEntitlementKeys(license);
|
// TODO: apply content_id_filter
|
||||||
if (key_array.empty()) {
|
|
||||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
|
||||||
"the entitlement contains no keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
status =
|
status =
|
||||||
crypto_session_->LoadLicense(serialized_license, core_message, signature);
|
crypto_session_->LoadLicense(serialized_license, core_message, signature);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
@@ -856,6 +811,36 @@ bool CasLicense::CanStoreLicense() const {
|
|||||||
return policy_engine_->CanPersist();
|
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(
|
CasStatus CasLicense::GenerateEntitlementRenewalRequest(
|
||||||
const std::string& device_certificate,
|
const std::string& device_certificate,
|
||||||
std::string* signed_renewal_request) {
|
std::string* signed_renewal_request) {
|
||||||
|
|||||||
@@ -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(
|
OEMCryptoResult CryptoInterface::OEMCrypto_LoadLicense(
|
||||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||||
size_t core_message_length, const uint8_t* signature,
|
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,
|
OEMCryptoResult CryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||||
size_t* idLength) {
|
size_t* idLength) {
|
||||||
return lock_->WithOecReadLock("GetDeviceID", [&] {
|
return lock_->WithOecReadLock("GetDeviceID", [&] {
|
||||||
@@ -828,64 +810,6 @@ CasStatus CryptoSession::DeriveKeysFromSessionKey(
|
|||||||
return CasStatus::OkStatus();
|
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,
|
CasStatus CryptoSession::LoadLicense(const std::string& signed_message,
|
||||||
const std::string& core_message,
|
const std::string& core_message,
|
||||||
const std::string& signature) {
|
const std::string& signature) {
|
||||||
@@ -1040,42 +964,6 @@ OEMCryptoResult CryptoSession::getCryptoInterface(
|
|||||||
return CryptoInterface::create(interface);
|
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) {
|
CasStatus CryptoSession::GetDeviceID(std::string* buffer) {
|
||||||
if (!crypto_interface_) {
|
if (!crypto_interface_) {
|
||||||
return CasStatus(CasStatusCode::kCryptoSessionError,
|
return CasStatus(CasStatusCode::kCryptoSessionError,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ int find_ecm_start_index(const CasEcm& cas_ecm) {
|
|||||||
|
|
||||||
} // namespace
|
} // 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.
|
// Detect and strip optional section header.
|
||||||
const int offset = find_ecm_start_index(cas_ecm);
|
const int offset = find_ecm_start_index(cas_ecm);
|
||||||
if (offset < 0 ||
|
if (offset < 0 ||
|
||||||
@@ -71,7 +71,7 @@ std::unique_ptr<const EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ecm[kVersionIndex] <= 2) {
|
if (ecm[kVersionIndex] <= 2) {
|
||||||
std::unique_ptr<const EcmParserV2> parser;
|
std::unique_ptr<EcmParserV2> parser;
|
||||||
if (!EcmParserV2::create(ecm, &parser)) {
|
if (!EcmParserV2::create(ecm, &parser)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,13 +80,12 @@ const EcmKeyData* EcmParserV2::key_slot_data(KeySlotId id) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool EcmParserV2::create(const CasEcm& cas_ecm,
|
bool EcmParserV2::create(const CasEcm& cas_ecm,
|
||||||
std::unique_ptr<const EcmParserV2>* parser) {
|
std::unique_ptr<EcmParserV2>* parser) {
|
||||||
if (parser == nullptr) {
|
if (parser == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Using 'new' to access a non-public constructor.
|
// Using 'new' to access a non-public constructor.
|
||||||
auto new_parser =
|
auto new_parser = std::unique_ptr<EcmParserV2>(new EcmParserV2(cas_ecm));
|
||||||
std::unique_ptr<const EcmParserV2>(new EcmParserV2(cas_ecm));
|
|
||||||
if (!new_parser->is_valid_size()) {
|
if (!new_parser->is_valid_size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,12 @@ CryptoMode ConvertProtoCipherMode(EcmMetaData::CipherMode cipher_mode) {
|
|||||||
EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload,
|
EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload,
|
||||||
EcmPayload ecm_payload)
|
EcmPayload ecm_payload)
|
||||||
: signed_ecm_payload_(std::move(signed_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) {
|
if (cas_ecm.size() <= kEcmHeaderSize) {
|
||||||
LOGE("ECM is too short. Size: %u", cas_ecm.size());
|
LOGE("ECM is too short. Size: %u", cas_ecm.size());
|
||||||
return nullptr;
|
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.
|
// 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));
|
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).
|
// Use the even entitlement_key_id if the odd one is empty (omitted).
|
||||||
const EcmKeyData& key_data =
|
const EcmKeyData& key_data =
|
||||||
id == KeySlotId::kOddKeySlot &&
|
id == KeySlotId::kOddKeySlot &&
|
||||||
!ecm_payload_.odd_key_data().entitlement_key_id().empty()
|
!odd_key_data_.entitlement_key_id().empty()
|
||||||
? ecm_payload_.odd_key_data()
|
? odd_key_data_
|
||||||
: ecm_payload_.even_key_data();
|
: even_key_data_;
|
||||||
|
|
||||||
return {key_data.entitlement_key_id().begin(),
|
return {key_data.entitlement_key_id().begin(),
|
||||||
key_data.entitlement_key_id().end()};
|
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 {
|
std::vector<uint8_t> EcmParserV3::wrapped_key_data(KeySlotId id) const {
|
||||||
const EcmKeyData& key_data = id == KeySlotId::kOddKeySlot
|
const EcmKeyData& key_data =
|
||||||
? ecm_payload_.odd_key_data()
|
id == KeySlotId::kOddKeySlot ? odd_key_data_ : even_key_data_;
|
||||||
: ecm_payload_.even_key_data();
|
|
||||||
|
|
||||||
return {key_data.wrapped_key_data().begin(),
|
return {key_data.wrapped_key_data().begin(),
|
||||||
key_data.wrapped_key_data().end()};
|
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 {
|
std::vector<uint8_t> EcmParserV3::wrapped_key_iv(KeySlotId id) const {
|
||||||
// Use the even wrapped_key_iv if the odd one is empty (omitted).
|
// Use the even wrapped_key_iv if the odd one is empty (omitted).
|
||||||
const EcmKeyData& key_data =
|
const EcmKeyData* key_data =
|
||||||
id == KeySlotId::kOddKeySlot &&
|
id == KeySlotId::kOddKeySlot && !odd_key_data_.wrapped_key_iv().empty()
|
||||||
!ecm_payload_.odd_key_data().wrapped_key_iv().empty()
|
? &odd_key_data_
|
||||||
? ecm_payload_.odd_key_data()
|
: &even_key_data_;
|
||||||
: ecm_payload_.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 {
|
std::vector<uint8_t> EcmParserV3::content_iv(KeySlotId id) const {
|
||||||
// Use the even content_iv if the odd one is empty (omitted).
|
// Use the even content_iv if the odd one is empty (omitted).
|
||||||
const EcmKeyData& key_data =
|
const EcmKeyData* key_data =
|
||||||
id == KeySlotId::kOddKeySlot &&
|
id == KeySlotId::kOddKeySlot && !odd_key_data_.content_iv().empty()
|
||||||
!ecm_payload_.odd_key_data().content_iv().empty()
|
? &odd_key_data_
|
||||||
? ecm_payload_.odd_key_data()
|
: &even_key_data_;
|
||||||
: ecm_payload_.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 {
|
bool EcmParserV3::has_fingerprinting() const {
|
||||||
@@ -171,4 +185,24 @@ std::string EcmParserV3::signature() const {
|
|||||||
return signed_ecm_payload_.signature();
|
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
|
} // namespace wvcas
|
||||||
|
|||||||
@@ -81,11 +81,6 @@ class OEMCryptoInterface::Impl {
|
|||||||
const uint8_t*, size_t,
|
const uint8_t*, size_t,
|
||||||
const uint8_t*, size_t,
|
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,
|
typedef OEMCryptoResult (*LoadLicense_t)(OEMCrypto_SESSION session,
|
||||||
const uint8_t* message,
|
const uint8_t* message,
|
||||||
size_t message_length,
|
size_t message_length,
|
||||||
@@ -106,10 +101,6 @@ class OEMCryptoInterface::Impl {
|
|||||||
size_t, OEMCryptoCipherMode);
|
size_t, OEMCryptoCipherMode);
|
||||||
typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*,
|
typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*,
|
||||||
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 (*GetDeviceID_t)(uint8_t* deviceID, size_t* idLength);
|
||||||
typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer,
|
typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer,
|
||||||
size_t length);
|
size_t length);
|
||||||
@@ -137,13 +128,11 @@ class OEMCryptoInterface::Impl {
|
|||||||
LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr;
|
LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr;
|
||||||
GenerateRSASignature_t GenerateRSASignature = nullptr;
|
GenerateRSASignature_t GenerateRSASignature = nullptr;
|
||||||
DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr;
|
DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr;
|
||||||
LoadKeys_t LoadKeys = nullptr;
|
|
||||||
LoadLicense_t LoadLicense = nullptr;
|
LoadLicense_t LoadLicense = nullptr;
|
||||||
LoadRenewal_t LoadRenewal = nullptr;
|
LoadRenewal_t LoadRenewal = nullptr;
|
||||||
LoadCasECMKeys_t LoadCasECMKeys = nullptr;
|
LoadCasECMKeys_t LoadCasECMKeys = nullptr;
|
||||||
SelectKey_t SelectKey = nullptr;
|
SelectKey_t SelectKey = nullptr;
|
||||||
GetHDCPCapability_t GetHDCPCapability = nullptr;
|
GetHDCPCapability_t GetHDCPCapability = nullptr;
|
||||||
RefreshKeys_t RefreshKeys = nullptr;
|
|
||||||
GetDeviceID_t GetDeviceID = nullptr;
|
GetDeviceID_t GetDeviceID = nullptr;
|
||||||
LoadTestKeybox_t LoadTestKeybox = nullptr;
|
LoadTestKeybox_t LoadTestKeybox = nullptr;
|
||||||
SecurityLevel_t SecurityLevel = nullptr;
|
SecurityLevel_t SecurityLevel = nullptr;
|
||||||
@@ -182,13 +171,11 @@ class OEMCryptoInterface::Impl {
|
|||||||
LOAD_SYM(LoadDRMPrivateKey);
|
LOAD_SYM(LoadDRMPrivateKey);
|
||||||
LOAD_SYM(GenerateRSASignature);
|
LOAD_SYM(GenerateRSASignature);
|
||||||
LOAD_SYM(DeriveKeysFromSessionKey);
|
LOAD_SYM(DeriveKeysFromSessionKey);
|
||||||
LOAD_SYM(LoadKeys);
|
|
||||||
LOAD_SYM(LoadLicense);
|
LOAD_SYM(LoadLicense);
|
||||||
LOAD_SYM(LoadRenewal);
|
LOAD_SYM(LoadRenewal);
|
||||||
LOAD_SYM(LoadCasECMKeys);
|
LOAD_SYM(LoadCasECMKeys);
|
||||||
LOAD_SYM(SelectKey);
|
LOAD_SYM(SelectKey);
|
||||||
LOAD_SYM(GetHDCPCapability);
|
LOAD_SYM(GetHDCPCapability);
|
||||||
LOAD_SYM(RefreshKeys);
|
|
||||||
LOAD_SYM(GetDeviceID);
|
LOAD_SYM(GetDeviceID);
|
||||||
LOAD_SYM(SecurityLevel);
|
LOAD_SYM(SecurityLevel);
|
||||||
LOAD_SYM(CreateEntitledKeySession);
|
LOAD_SYM(CreateEntitledKeySession);
|
||||||
@@ -328,17 +315,6 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_DeriveKeysFromSessionKey(
|
|||||||
mac_key_context_length, enc_key_context, enc_key_context_length);
|
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(
|
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadLicense(
|
||||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||||
size_t core_message_length, const uint8_t* signature,
|
size_t core_message_length, const uint8_t* signature,
|
||||||
@@ -375,14 +351,6 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability(
|
|||||||
return impl_->GetHDCPCapability(current, max);
|
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,
|
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||||
size_t* idLength) {
|
size_t* idLength) {
|
||||||
return impl_->GetDeviceID(deviceID, idLength);
|
return impl_->GetDeviceID(deviceID, idLength);
|
||||||
|
|||||||
@@ -76,6 +76,46 @@ std::string GenerateLicenseFilename(const std::string& content_id,
|
|||||||
return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) +
|
return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) +
|
||||||
std::string(kLicenseFileNameSuffix));
|
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
|
||||||
|
|
||||||
namespace wvcas {
|
namespace wvcas {
|
||||||
@@ -129,8 +169,7 @@ void WidevineCas::OnTimerEvent() {
|
|||||||
|
|
||||||
// Delete expired license after firing expired event in policy_engine
|
// Delete expired license after firing expired event in policy_engine
|
||||||
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
|
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
|
||||||
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
|
std::string filename = license_id_ + kLicenseFileNameSuffix;
|
||||||
media_id_->provider_id());
|
|
||||||
if (!file_system_->Exists(filename)) {
|
if (!file_system_->Exists(filename)) {
|
||||||
LOGI("No expired license file stored in disk");
|
LOGI("No expired license file stored in disk");
|
||||||
} else {
|
} else {
|
||||||
@@ -238,7 +277,8 @@ CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId,
|
|||||||
}
|
}
|
||||||
uint8_t ecm_age_previous = session->GetEcmAgeRestriction();
|
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();
|
uint8_t ecm_age_current = session->GetEcmAgeRestriction();
|
||||||
if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) {
|
if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) {
|
||||||
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
|
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
|
||||||
@@ -302,12 +342,24 @@ CasStatus WidevineCas::generateEntitlementRequest(
|
|||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
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 license_file;
|
||||||
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
|
|
||||||
media_id_->provider_id());
|
|
||||||
if (ReadFileFromStorage(*file_system_, filename, &license_file)) {
|
if (ReadFileFromStorage(*file_system_, filename, &license_file)) {
|
||||||
CasStatus status =
|
status = cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file,
|
||||||
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
|
/*content_id_filter=*/nullptr);
|
||||||
if (status.ok()) {
|
if (status.ok()) {
|
||||||
// If license file is expired, don't proceed the request. Also
|
// If license file is expired, don't proceed the request. Also
|
||||||
// delete the stored license file.
|
// delete the stored license file.
|
||||||
@@ -323,6 +375,13 @@ CasStatus WidevineCas::generateEntitlementRequest(
|
|||||||
}
|
}
|
||||||
license_id =
|
license_id =
|
||||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
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);
|
policy_timer_.Start(this, 1);
|
||||||
has_license_ = true;
|
has_license_ = true;
|
||||||
return HandleDeferredECMs();
|
return HandleDeferredECMs();
|
||||||
@@ -340,20 +399,29 @@ CasStatus WidevineCas::generateEntitlementRequest(
|
|||||||
entitlement_request);
|
entitlement_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
|
CasStatus WidevineCas::handleEntitlementResponse(
|
||||||
std::string& license_id) {
|
const std::string& response, std::string& license_id,
|
||||||
|
std::string& multi_content_license_info, std::string& group_license_info) {
|
||||||
if (response.empty()) {
|
if (response.empty()) {
|
||||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||||
"empty entitlement response");
|
"empty entitlement response");
|
||||||
}
|
}
|
||||||
|
if (media_id_ == nullptr) {
|
||||||
|
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
|
||||||
|
}
|
||||||
|
|
||||||
std::string device_file;
|
std::string device_file;
|
||||||
std::unique_lock<std::mutex> locker(lock_);
|
std::unique_lock<std::mutex> locker(lock_);
|
||||||
CasStatus status =
|
CasStatus status = cas_license_->HandleEntitlementResponse(
|
||||||
cas_license_->HandleEntitlementResponse(response, &device_file);
|
response, /*content_id_filter=*/nullptr, &device_file);
|
||||||
if (status.ok()) {
|
if (status.ok()) {
|
||||||
// A license has been successfully loaded. Load any ecms that may have been
|
// A license has been successfully loaded. Load any ecms that may have been
|
||||||
// deferred waiting for the license.
|
// deferred waiting for the license.
|
||||||
|
if (cas_license_->IsGroupLicense()) {
|
||||||
|
license_group_id_ = cas_license_->GetGroupId();
|
||||||
|
}
|
||||||
has_license_ = true;
|
has_license_ = true;
|
||||||
|
|
||||||
status = HandleDeferredECMs();
|
status = HandleDeferredECMs();
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
@@ -361,17 +429,27 @@ CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
|
|||||||
|
|
||||||
policy_timer_.Start(this, 1);
|
policy_timer_.Start(this, 1);
|
||||||
|
|
||||||
if (device_file.empty()) {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device_file.empty()) {
|
if (!device_file.empty()) {
|
||||||
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
|
const std::string license_group_id = cas_license_->GetGroupId();
|
||||||
media_id_->provider_id());
|
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);
|
StoreFile(*file_system_, filename, device_file);
|
||||||
// license_id will be the filename without ".lic" extension.
|
// license_id will be the filename without ".lic" extension.
|
||||||
license_id =
|
license_id =
|
||||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
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;
|
return status;
|
||||||
@@ -400,21 +478,25 @@ CasStatus WidevineCas::handleEntitlementRenewalResponse(
|
|||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
if (!device_file.empty()) {
|
if (!device_file.empty() && media_id_ != nullptr) {
|
||||||
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
|
const std::string license_group_id = cas_license_->GetGroupId();
|
||||||
media_id_->provider_id());
|
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);
|
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 will be the filename without ".lic" extension.
|
||||||
license_id =
|
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;
|
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,
|
// 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.
|
// 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");
|
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
|
||||||
}
|
}
|
||||||
// Remove the license file given the file_name user provides.
|
// 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");
|
"unable to remove license file from disk");
|
||||||
}
|
}
|
||||||
LOGI("Remove license file from disk successfully.");
|
LOGI("Remove license file from disk successfully.");
|
||||||
std::string used_license_filename = GenerateLicenseFilename(
|
|
||||||
media_id_->content_id(), media_id_->provider_id());
|
std::string current_license_filename = license_id_ + kLicenseFileNameSuffix;
|
||||||
if (file_name.compare(used_license_filename) == 0) {
|
if (file_name == current_license_filename) {
|
||||||
// Update license policy for the in-used license. Plugin will not allowed to
|
// 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
|
// play stream, store and renew license unless a new plugin instance is
|
||||||
// created.
|
// created.
|
||||||
@@ -491,7 +573,13 @@ CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) {
|
|||||||
"missing value of parental control min age");
|
"missing value of parental control min age");
|
||||||
}
|
}
|
||||||
parental_control_age_ = data[0];
|
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;
|
return CasStatusCode::kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,21 +42,19 @@ CasStatus WidevineCasSession::initialize(
|
|||||||
return CasStatusCode::kNoError;
|
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,
|
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_) {
|
if (ecm != current_ecm_) {
|
||||||
LOGD("WidevineCasSession::processEcm: received new 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) {
|
if (ecm_parser == nullptr) {
|
||||||
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
|
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();
|
ecm_age_restriction_ = ecm_parser->age_restriction();
|
||||||
// Parental control check.
|
// Parental control check.
|
||||||
@@ -114,7 +112,8 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
|||||||
// Temporary key slots to only have successfully loaded keys in |keys_|.
|
// Temporary key slots to only have successfully loaded keys in |keys_|.
|
||||||
CasKeySlotData keys;
|
CasKeySlotData keys;
|
||||||
do {
|
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];
|
KeySlot& key = keys[keyslot_id];
|
||||||
key.key_id = ecm_parser->content_key_id(keyslot_id);
|
key.key_id = ecm_parser->content_key_id(keyslot_id);
|
||||||
key.wrapped_key = ecm_parser->wrapped_key_data(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;
|
return CasStatusCode::kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<const EcmParser> WidevineCasSession::getEcmParser(
|
std::unique_ptr<EcmParser> WidevineCasSession::getEcmParser(
|
||||||
const CasEcm& ecm) const {
|
const CasEcm& ecm) const {
|
||||||
return EcmParser::Create(ecm);
|
return EcmParser::Create(ecm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <utils/String8.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -54,22 +55,28 @@ CasSessionId widevineSessionIdToAndroid(const WvCasSessionId& wv_session_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback)
|
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,
|
WidevineCasPlugin::WidevineCasPlugin(void* appData,
|
||||||
CasPluginCallbackExt callback)
|
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() {
|
status_t WidevineCasPlugin::initialize() {
|
||||||
CasStatus status = widevine_cas_.initialize(this);
|
CasStatus status = widevine_cas_api_->initialize(this);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return android::ERROR_CAS_UNKNOWN;
|
return android::ERROR_CAS_UNKNOWN;
|
||||||
}
|
}
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WidevineCasPlugin::is_provisioned() {
|
bool WidevineCasPlugin::is_provisioned() const {
|
||||||
return widevine_cas_.is_provisioned();
|
return widevine_cas_api_->is_provisioned();
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t WidevineCasPlugin::setStatusCallback(
|
status_t WidevineCasPlugin::setStatusCallback(
|
||||||
@@ -86,11 +93,11 @@ status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
CasStatus status =
|
CasStatus status =
|
||||||
widevine_cas_.ProcessCAPrivateData(privateData, &provision_data_);
|
widevine_cas_api_->ProcessCAPrivateData(privateData, &provision_data_);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return android::ERROR_CAS_UNKNOWN;
|
return android::ERROR_CAS_UNKNOWN;
|
||||||
}
|
}
|
||||||
if (widevine_cas_.is_provisioned()) {
|
if (widevine_cas_api_->is_provisioned()) {
|
||||||
return requestLicense(provision_data_);
|
return requestLicense(provision_data_);
|
||||||
}
|
}
|
||||||
return OK;
|
return OK;
|
||||||
@@ -100,8 +107,13 @@ status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) {
|
|||||||
if (nullptr == sessionId) {
|
if (nullptr == sessionId) {
|
||||||
return BAD_VALUE;
|
return BAD_VALUE;
|
||||||
}
|
}
|
||||||
|
if (!is_provisioned()) {
|
||||||
|
LOGE("Sessions can only be opened after privisioned.");
|
||||||
|
return android::ERROR_CAS_NOT_PROVISIONED;
|
||||||
|
}
|
||||||
|
|
||||||
WvCasSessionId new_session_id;
|
WvCasSessionId new_session_id;
|
||||||
CasStatus status = widevine_cas_.openSession(&new_session_id);
|
CasStatus status = widevine_cas_api_->openSession(&new_session_id);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
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) {
|
status_t WidevineCasPlugin::closeSession(const CasSessionId& sessionId) {
|
||||||
WvCasSessionId wv_session_id = androidSessionIdToWidevine(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()) {
|
if (!status.ok()) {
|
||||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
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.
|
// Doesn't matter which session, CA descriptor applies to all of them.
|
||||||
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
|
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
|
||||||
CasStatus status = widevine_cas_.ProcessSessionCAPrivateData(
|
CasStatus status = widevine_cas_api_->ProcessSessionCAPrivateData(
|
||||||
wv_session_id, privateData, &provision_data_);
|
wv_session_id, privateData, &provision_data_);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
||||||
}
|
}
|
||||||
if (widevine_cas_.is_provisioned()) {
|
if (widevine_cas_api_->is_provisioned()) {
|
||||||
return requestLicense(provision_data_);
|
return requestLicense(provision_data_);
|
||||||
}
|
}
|
||||||
return OK;
|
return OK;
|
||||||
@@ -155,7 +167,7 @@ status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
|
|||||||
const CasEcm& ecm) {
|
const CasEcm& ecm) {
|
||||||
LOGI("WidevineCasPlugin::processEcm");
|
LOGI("WidevineCasPlugin::processEcm");
|
||||||
WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId);
|
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()) {
|
if (!status.ok()) {
|
||||||
CasData error(status.error_string().begin(), status.error_string().end());
|
CasData error(status.error_string().begin(), status.error_string().end());
|
||||||
switch (status.status_code()) {
|
switch (status.status_code()) {
|
||||||
@@ -227,8 +239,8 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string provisioning_request;
|
std::string provisioning_request;
|
||||||
CasStatus status =
|
CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest(
|
||||||
widevine_cas_.generateDeviceProvisioningRequest(&provisioning_request);
|
&provisioning_request);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return INVALID_OPERATION;
|
return INVALID_OPERATION;
|
||||||
}
|
}
|
||||||
@@ -246,7 +258,7 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
|
|||||||
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
|
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
|
||||||
std::string signed_license_request;
|
std::string signed_license_request;
|
||||||
std::string license_id;
|
std::string license_id;
|
||||||
CasStatus status = widevine_cas_.generateEntitlementRequest(
|
CasStatus status = widevine_cas_api_->generateEntitlementRequest(
|
||||||
init_data, &signed_license_request, license_id);
|
init_data, &signed_license_request, license_id);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return INVALID_OPERATION;
|
return INVALID_OPERATION;
|
||||||
@@ -273,10 +285,6 @@ status_t WidevineCasPlugin::refreshEntitlements(
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<CryptoSession> WidevineCasPlugin::getCryptoSession() {
|
|
||||||
return std::make_shared<CryptoSession>();
|
|
||||||
}
|
|
||||||
|
|
||||||
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
|
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
|
||||||
const CasData& eventData,
|
const CasData& eventData,
|
||||||
const CasSessionId* sessionId) {
|
const CasSessionId* sessionId) {
|
||||||
@@ -297,6 +305,8 @@ CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
|
|||||||
return HandleSetParentalControlAge(eventData);
|
return HandleSetParentalControlAge(eventData);
|
||||||
case LICENSE_REMOVAL:
|
case LICENSE_REMOVAL:
|
||||||
return HandleLicenseRemoval(eventData);
|
return HandleLicenseRemoval(eventData);
|
||||||
|
case ASSIGN_LICENSE_ID:
|
||||||
|
return HandleAssignLicenseID(eventData);
|
||||||
default:
|
default:
|
||||||
return CasStatusCode::kUnknownEvent;
|
return CasStatusCode::kUnknownEvent;
|
||||||
}
|
}
|
||||||
@@ -310,7 +320,7 @@ CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
|
|||||||
"empty individualization response");
|
"empty individualization response");
|
||||||
}
|
}
|
||||||
std::string resp_string(response.begin(), response.end());
|
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()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -335,14 +345,28 @@ CasStatus WidevineCasPlugin::HandleEntitlementResponse(
|
|||||||
}
|
}
|
||||||
std::string resp_string(response.begin(), response.end());
|
std::string resp_string(response.begin(), response.end());
|
||||||
std::string license_id;
|
std::string license_id;
|
||||||
CasStatus status =
|
std::string multi_content_license_info;
|
||||||
widevine_cas_.handleEntitlementResponse(resp_string, license_id);
|
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()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
|
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
|
||||||
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
|
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
|
||||||
license_id.size(), nullptr);
|
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;
|
return CasStatusCode::kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,8 +377,8 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
|
|||||||
}
|
}
|
||||||
std::string resp_string(response.begin(), response.end());
|
std::string resp_string(response.begin(), response.end());
|
||||||
std::string license_id;
|
std::string license_id;
|
||||||
CasStatus status =
|
CasStatus status = widevine_cas_api_->handleEntitlementRenewalResponse(
|
||||||
widevine_cas_.handleEntitlementRenewalResponse(resp_string, license_id);
|
resp_string, license_id);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -367,7 +391,7 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
|
|||||||
|
|
||||||
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
|
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
CasStatus status = widevine_cas_.GetUniqueID(&buffer);
|
CasStatus status = widevine_cas_api_->GetUniqueID(&buffer);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
CasData error(status.error_string().begin(), status.error_string().end());
|
CasData error(status.error_string().begin(), status.error_string().end());
|
||||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||||
@@ -383,7 +407,7 @@ CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
|
CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
|
||||||
return widevine_cas_.HandleSetParentalControlAge(data);
|
return widevine_cas_api_->HandleSetParentalControlAge(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
|
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 license_id_str(license_id.begin(), license_id.end());
|
||||||
|
|
||||||
std::string file_name = license_id_str + ".lic";
|
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()) {
|
if (!status.ok()) {
|
||||||
CasData error(status.error_string().begin(), status.error_string().end());
|
CasData error(status.error_string().begin(), status.error_string().end());
|
||||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||||
@@ -407,11 +431,27 @@ CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
|
|||||||
return CasStatusCode::kNoError;
|
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() {
|
void WidevineCasPlugin::OnSessionRenewalNeeded() {
|
||||||
LOGI("OnSessionRenewalNeeded");
|
LOGI("OnSessionRenewalNeeded");
|
||||||
std::string renewal_request;
|
std::string renewal_request;
|
||||||
CasStatus status =
|
CasStatus status =
|
||||||
widevine_cas_.generateEntitlementRenewalRequest(&renewal_request);
|
widevine_cas_api_->generateEntitlementRenewalRequest(&renewal_request);
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
LOGE("unable to generate a license renewal request: %s",
|
LOGE("unable to generate a license renewal request: %s",
|
||||||
status.error_string().c_str());
|
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) {
|
void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) {
|
||||||
LOGI("OnExpirationUpdate");
|
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(
|
void WidevineCasPlugin::OnNewRenewalServerUrl(
|
||||||
|
|||||||
@@ -49,6 +49,34 @@ message LicenseIdentification {
|
|||||||
optional bytes provider_session_token = 6;
|
optional bytes provider_session_token = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This message is used to indicate the license cateogry spec for a license as
|
||||||
|
// a part of initial license issuance.
|
||||||
|
message LicenseCategorySpec {
|
||||||
|
// Possible license categories.
|
||||||
|
enum LicenseCategory {
|
||||||
|
// By default, License is used for single content.
|
||||||
|
SINGLE_CONTENT_LICENSE_DEFAULT = 0;
|
||||||
|
// License is used for multiple contents (could be a combination of
|
||||||
|
// single contents and groups of contents).
|
||||||
|
MULTI_CONTENT_LICENSE = 1;
|
||||||
|
// License is used for contents logically grouped.
|
||||||
|
GROUP_LICENSE = 2;
|
||||||
|
}
|
||||||
|
// Optional. License category indicates if license is used for single
|
||||||
|
// content, multiple contents (could be a combination of
|
||||||
|
// single contents and groups of contents) or a group of contents.
|
||||||
|
optional LicenseCategory license_category = 1;
|
||||||
|
// Optional. Content or group ID covered by the license.
|
||||||
|
oneof content_or_group_id {
|
||||||
|
// Content_id would be present if it is a license for single content.
|
||||||
|
bytes content_id = 2;
|
||||||
|
// Group_id would be present if the license is a multi_content_license or
|
||||||
|
// group_license. Group Id could be the name of a group of contents,
|
||||||
|
// defined by licensor.
|
||||||
|
bytes group_id = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message License {
|
message License {
|
||||||
message Policy {
|
message Policy {
|
||||||
// Indicates that playback of the content is allowed.
|
// Indicates that playback of the content is allowed.
|
||||||
@@ -217,6 +245,27 @@ message License {
|
|||||||
optional bool allow_signature_verify = 4 [default = false];
|
optional bool allow_signature_verify = 4 [default = false];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyCategorySpec message is used to identify if current key is generated
|
||||||
|
// for a single content or a group of contents.
|
||||||
|
message KeyCategorySpec {
|
||||||
|
// Represents what kind of content a key is used for.
|
||||||
|
enum KeyCategory {
|
||||||
|
// By default, key is created for single content.
|
||||||
|
SINGLE_CONTENT_KEY_DEFAULT = 0;
|
||||||
|
// Key is created for a group of contents.
|
||||||
|
GROUP_KEY = 1;
|
||||||
|
}
|
||||||
|
// Indicate if the current key is created for single content or for group
|
||||||
|
// use.
|
||||||
|
optional KeyCategory key_category = 1;
|
||||||
|
// Id for key category. If it is a key for single content, this id
|
||||||
|
// represents the content_id. Otherwise, it represents a group_id.
|
||||||
|
oneof content_or_group_id {
|
||||||
|
bytes content_id = 2;
|
||||||
|
bytes group_id = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
optional bytes id = 1;
|
optional bytes id = 1;
|
||||||
optional bytes iv = 2;
|
optional bytes iv = 2;
|
||||||
optional bytes key = 3;
|
optional bytes key = 3;
|
||||||
@@ -242,6 +291,9 @@ message License {
|
|||||||
// Optional not limited to commonly known track types such as SD, HD.
|
// Optional not limited to commonly known track types such as SD, HD.
|
||||||
// It can be some provider defined label to identify the track.
|
// It can be some provider defined label to identify the track.
|
||||||
optional string track_label = 12;
|
optional string track_label = 12;
|
||||||
|
// A Key Category Spec is used to identify if current key is generated for a
|
||||||
|
// single content or a group of contents.
|
||||||
|
optional KeyCategorySpec key_category_spec = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional LicenseIdentification id = 1;
|
optional LicenseIdentification id = 1;
|
||||||
@@ -275,6 +327,10 @@ message License {
|
|||||||
[default = PLATFORM_NO_VERIFICATION];
|
[default = PLATFORM_NO_VERIFICATION];
|
||||||
// IDs of the groups for which keys are delivered in this license, if any.
|
// IDs of the groups for which keys are delivered in this license, if any.
|
||||||
repeated bytes group_ids = 11;
|
repeated bytes group_ids = 11;
|
||||||
|
// Optional. LicenseCategorySpec is used to indicate the license category for
|
||||||
|
// a license. It could be used as a part of initial license issuance or shown
|
||||||
|
// as a part of license in license response.
|
||||||
|
optional LicenseCategorySpec license_category_spec = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProtocolVersion {
|
enum ProtocolVersion {
|
||||||
@@ -1038,6 +1094,15 @@ message CASDrmLicenseRequest {
|
|||||||
// Optionally specify even, odd or single slot for key rotation.
|
// Optionally specify even, odd or single slot for key rotation.
|
||||||
repeated CASEncryptionResponse.KeyInfo entitlement_keys = 4;
|
repeated CASEncryptionResponse.KeyInfo entitlement_keys = 4;
|
||||||
optional License.KeyContainer.KeyType key_type = 5;
|
optional License.KeyContainer.KeyType key_type = 5;
|
||||||
|
// A track type is used to represent a set of tracks that share the same
|
||||||
|
// content key and security level. Common values are SD, HD, UHD1, UHD2
|
||||||
|
// and AUDIO. Content providers may use arbitrary strings for track type
|
||||||
|
// as long as they are consistent with the track types used at packaging
|
||||||
|
// time.
|
||||||
|
optional string track_type = 6;
|
||||||
|
// A Key Category Spec is used to identify if current key is generated for a
|
||||||
|
// single content or a group of contents.
|
||||||
|
optional License.KeyContainer.KeyCategorySpec key_category_spec = 7;
|
||||||
}
|
}
|
||||||
repeated ContentKeySpec content_key_specs = 4;
|
repeated ContentKeySpec content_key_specs = 4;
|
||||||
// Policy for the entire license such as playback duration.
|
// Policy for the entire license such as playback duration.
|
||||||
@@ -1053,6 +1118,10 @@ message CASEncryptionRequest {
|
|||||||
// return one key for EVEN and one key for ODD, otherwise only a single key is
|
// return one key for EVEN and one key for ODD, otherwise only a single key is
|
||||||
// returned.
|
// returned.
|
||||||
optional bool key_rotation = 4;
|
optional bool key_rotation = 4;
|
||||||
|
// Optional value which can be used to indicate a group.
|
||||||
|
// If present the CasEncryptionResponse will return key based on the group
|
||||||
|
// id.
|
||||||
|
optional bytes group_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CASEncryptionResponse {
|
message CASEncryptionResponse {
|
||||||
@@ -1084,6 +1153,9 @@ message CASEncryptionResponse {
|
|||||||
optional string status_message = 2;
|
optional string status_message = 2;
|
||||||
optional bytes content_id = 3;
|
optional bytes content_id = 3;
|
||||||
repeated KeyInfo entitlement_keys = 4;
|
repeated KeyInfo entitlement_keys = 4;
|
||||||
|
// If keys shown in the encryption response are for group usage, this is the
|
||||||
|
// group identifier.
|
||||||
|
optional bytes group_id = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SignedCASEncryptionRequest {
|
message SignedCASEncryptionRequest {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ message CaDescriptorPrivateData {
|
|||||||
|
|
||||||
// Content ID.
|
// Content ID.
|
||||||
optional bytes content_id = 2;
|
optional bytes content_id = 2;
|
||||||
|
|
||||||
|
// Entitlement key IDs for current content per track. Each track will allow up
|
||||||
|
// to 2 entitlement key ids (odd and even entitlement keys).
|
||||||
|
repeated bytes entitlement_key_ids = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widevine fingerprinting.
|
// Widevine fingerprinting.
|
||||||
@@ -76,6 +80,18 @@ message EcmKeyData {
|
|||||||
optional bytes content_iv = 4;
|
optional bytes content_iv = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message EcmGroupKeyData {
|
||||||
|
// Group id of this key data.
|
||||||
|
optional bytes group_id = 1;
|
||||||
|
// Required. The key data for the even slot. Fields wrapped_key_iv and
|
||||||
|
// content_iv may be omitted if it is the same as EcmPayload.even_key_data.
|
||||||
|
optional EcmKeyData even_key_data = 2;
|
||||||
|
// Optional. The key data for the odd slot if key rotation is enabled. Fields
|
||||||
|
// wrapped_key_iv and content_iv may be omitted if it is the same as
|
||||||
|
// EcmPayload.odd_key_data.
|
||||||
|
optional EcmKeyData odd_key_data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message EcmPayload {
|
message EcmPayload {
|
||||||
// Required. Meta info carried by the ECM.
|
// Required. Meta info carried by the ECM.
|
||||||
optional EcmMetaData meta_data = 1;
|
optional EcmMetaData meta_data = 1;
|
||||||
@@ -87,6 +103,9 @@ message EcmPayload {
|
|||||||
optional Fingerprinting fingerprinting = 4;
|
optional Fingerprinting fingerprinting = 4;
|
||||||
// Optional. Widevine service blocking information.
|
// Optional. Widevine service blocking information.
|
||||||
optional ServiceBlocking service_blocking = 5;
|
optional ServiceBlocking service_blocking = 5;
|
||||||
|
// If a channel belongs to a group, the content keys can additionally be
|
||||||
|
// encrypted by the group entitlement keys.
|
||||||
|
repeated EcmGroupKeyData group_key_data = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The payload field for an ECM with signature.
|
// The payload field for an ECM with signature.
|
||||||
|
|||||||
@@ -12,19 +12,11 @@
|
|||||||
|
|
||||||
#include "cas_status.h"
|
#include "cas_status.h"
|
||||||
#include "cas_util.h"
|
#include "cas_util.h"
|
||||||
#include "crypto_key.h"
|
|
||||||
#include "device_files.pb.h"
|
#include "device_files.pb.h"
|
||||||
#include "license_protocol.pb.h"
|
#include "license_protocol.pb.h"
|
||||||
#include "mock_crypto_session.h"
|
#include "mock_crypto_session.h"
|
||||||
#include "string_conversions.h"
|
#include "string_conversions.h"
|
||||||
|
|
||||||
// Prototype for ExtractEntitlementKeys. This prototype is added here to allow
|
|
||||||
// this method to be unit tested without being added to CasLicense header.
|
|
||||||
namespace wvcas {
|
|
||||||
std::vector<wvcas::CryptoKey> ExtractEntitlementKeys(
|
|
||||||
const video_widevine::License& license);
|
|
||||||
} // namespace wvcas
|
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::AllOf;
|
using ::testing::AllOf;
|
||||||
using ::testing::DoAll;
|
using ::testing::DoAll;
|
||||||
@@ -211,41 +203,6 @@ std::string CreateLicenseFileData() {
|
|||||||
return hashed_file.SerializeAsString();
|
return hashed_file.SerializeAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CasLicenseUtilityTest, ExtractEntitlementKeys) {
|
|
||||||
video_widevine::License license;
|
|
||||||
|
|
||||||
auto* key = license.add_key();
|
|
||||||
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
|
|
||||||
key->set_id(kKeyIDVideo);
|
|
||||||
key->set_iv(kKeyVideoIV);
|
|
||||||
key->mutable_key_control()->set_key_control_block(kKeyControlVideo);
|
|
||||||
key->mutable_key_control()->set_iv(kKeyControlIVVideo);
|
|
||||||
key->set_track_label(kTrackTypeVideo);
|
|
||||||
|
|
||||||
key = license.add_key();
|
|
||||||
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
|
|
||||||
key->set_id(kKeyIDAudio);
|
|
||||||
key->set_iv(kKeyAudioIV);
|
|
||||||
key->mutable_key_control()->set_key_control_block(kKeyControlAudio);
|
|
||||||
key->mutable_key_control()->set_iv(kKeyControlIVAudio);
|
|
||||||
key->set_track_label(kTrackTypeAudio);
|
|
||||||
|
|
||||||
std::vector<wvcas::CryptoKey> keys = wvcas::ExtractEntitlementKeys(license);
|
|
||||||
ASSERT_EQ(2, keys.size());
|
|
||||||
|
|
||||||
EXPECT_EQ(kKeyIDVideo, keys[0].key_id());
|
|
||||||
EXPECT_EQ(kKeyVideoIV, keys[0].key_data_iv());
|
|
||||||
EXPECT_EQ(kKeyControlVideo, keys[0].key_control());
|
|
||||||
EXPECT_EQ(kKeyControlIVVideo, keys[0].key_control_iv());
|
|
||||||
EXPECT_EQ(kTrackTypeVideo, keys[0].track_label());
|
|
||||||
|
|
||||||
EXPECT_EQ(kKeyIDAudio, keys[1].key_id());
|
|
||||||
EXPECT_EQ(kKeyAudioIV, keys[1].key_data_iv());
|
|
||||||
EXPECT_EQ(kKeyControlAudio, keys[1].key_control());
|
|
||||||
EXPECT_EQ(kKeyControlIVAudio, keys[1].key_control_iv());
|
|
||||||
EXPECT_EQ(kTrackTypeAudio, keys[1].track_label());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
|
TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
|
||||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||||
TestCasLicense cas_license;
|
TestCasLicense cas_license;
|
||||||
@@ -409,24 +366,31 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
|||||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||||
.WillOnce(Return(false));
|
.WillOnce(Return(false));
|
||||||
status = cas_license.HandleEntitlementResponse(entitlement_response, nullptr);
|
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||||
|
/*content_id_filter=*/nullptr,
|
||||||
|
/*device_file=*/nullptr);
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||||
|
// Not a group license.
|
||||||
|
EXPECT_TRUE(cas_license.GetGroupId().empty());
|
||||||
|
EXPECT_TRUE(cas_license.GetContentIdList().empty());
|
||||||
|
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||||
|
EXPECT_FALSE(cas_license.IsMultiContentLicense());
|
||||||
|
|
||||||
// Valid with device file.
|
// Valid with device file.
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||||
.WillOnce(Return(false));
|
.WillOnce(Return(false));
|
||||||
std::string device_file;
|
std::string device_file;
|
||||||
status =
|
status = cas_license.HandleEntitlementResponse(
|
||||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
|
||||||
EXPECT_TRUE(device_file.empty());
|
EXPECT_TRUE(device_file.empty());
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||||
|
|
||||||
// Valid with device file and can_persist = true.
|
// Valid with device file and can_persist = true.
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||||
status =
|
status = cas_license.HandleEntitlementResponse(
|
||||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
|
||||||
EXPECT_FALSE(device_file.empty());
|
EXPECT_FALSE(device_file.empty());
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||||
|
|
||||||
@@ -632,6 +596,79 @@ TEST_F(CasLicenseTest, RestoreLicense) {
|
|||||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data)
|
cas_license
|
||||||
|
.HandleStoredLicense(wrapped_rsa_key_, license_file_data,
|
||||||
|
/*content_id_filter=*/nullptr)
|
||||||
.status_code());
|
.status_code());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
|
||||||
|
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||||
|
TestCasLicense cas_license;
|
||||||
|
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||||
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
|
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||||
|
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||||
|
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||||
|
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||||
|
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||||
|
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||||
|
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||||
|
EXPECT_CALL(*strict_mock_,
|
||||||
|
PrepareAndSignLicenseRequest(_, NotNull(), NotNull()))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||||
|
Return(wvcas::CasStatusCode::kNoError)));
|
||||||
|
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||||
|
|
||||||
|
std::string serialized_entitlement_request;
|
||||||
|
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||||
|
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||||
|
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||||
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||||
|
|
||||||
|
// Create multi content entitlement response.
|
||||||
|
video_widevine::LicenseCategorySpec license_category_spec;
|
||||||
|
license_category_spec.set_license_category(
|
||||||
|
video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE);
|
||||||
|
license_category_spec.set_group_id("group_id");
|
||||||
|
video_widevine::License license;
|
||||||
|
*license.mutable_license_category_spec() = license_category_spec;
|
||||||
|
video_widevine::License::KeyContainer::KeyCategorySpec key_category_spec;
|
||||||
|
key_category_spec.set_content_id("content_id_1");
|
||||||
|
auto* key = license.add_key();
|
||||||
|
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||||
|
*key->mutable_key_category_spec() = key_category_spec;
|
||||||
|
key = license.add_key();
|
||||||
|
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||||
|
*key->mutable_key_category_spec() = key_category_spec;
|
||||||
|
key = license.add_key();
|
||||||
|
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||||
|
key_category_spec.set_content_id("content_id_2");
|
||||||
|
*key->mutable_key_category_spec() = key_category_spec;
|
||||||
|
|
||||||
|
video_widevine::SignedMessage signed_message;
|
||||||
|
license.SerializeToString(signed_message.mutable_msg());
|
||||||
|
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||||
|
signed_message.set_signature(kExpectedSignature);
|
||||||
|
signed_message.set_session_key(kExpectedSignature);
|
||||||
|
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||||
|
std::string entitlement_response = signed_message.SerializeAsString();
|
||||||
|
|
||||||
|
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||||
|
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||||
|
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||||
|
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||||
|
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||||
|
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||||
|
|
||||||
|
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||||
|
/*content_id_filter=*/nullptr,
|
||||||
|
/*device_file=*/nullptr);
|
||||||
|
EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError);
|
||||||
|
// Not a group license.
|
||||||
|
EXPECT_EQ(cas_license.GetGroupId(), "group_id");
|
||||||
|
EXPECT_THAT(cas_license.GetContentIdList(),
|
||||||
|
testing::ElementsAre("content_id_1", "content_id_2"));
|
||||||
|
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||||
|
EXPECT_TRUE(cas_license.IsMultiContentLicense());
|
||||||
}
|
}
|
||||||
@@ -49,12 +49,6 @@ static const std::string kOddWrappedKeyIv("odd_wrapped_content_key_iv");
|
|||||||
static const std::string kEvenContentIv("even_content_iv");
|
static const std::string kEvenContentIv("even_content_iv");
|
||||||
static const std::string kOddContentIv("odd_content_iv");
|
static const std::string kOddContentIv("odd_content_iv");
|
||||||
|
|
||||||
// Defined in cas_license.cpp.
|
|
||||||
namespace wvcas {
|
|
||||||
extern std::vector<CryptoKey> ExtractKeyControlKeys(
|
|
||||||
const video_widevine::License& license);
|
|
||||||
} // namespace wvcas
|
|
||||||
|
|
||||||
// TODO(jfore): Add validation of arg->buffer based on type. Type is assumed to
|
// TODO(jfore): Add validation of arg->buffer based on type. Type is assumed to
|
||||||
// be clear.
|
// be clear.
|
||||||
MATCHER_P2(IsValidOutputBuffer, type, dest, "") {
|
MATCHER_P2(IsValidOutputBuffer, type, dest, "") {
|
||||||
@@ -271,12 +265,6 @@ class MockedOEMCrypto : public wvcas::OEMCryptoInterface {
|
|||||||
const uint8_t* content_key_id,
|
const uint8_t* content_key_id,
|
||||||
size_t content_key_id_length,
|
size_t content_key_id_length,
|
||||||
OEMCryptoCipherMode cipher_mode));
|
OEMCryptoCipherMode cipher_mode));
|
||||||
MOCK_METHOD7(OEMCrypto_RefreshKeys,
|
|
||||||
OEMCryptoResult(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));
|
|
||||||
MOCK_METHOD2(OEMCrypto_GetDeviceID,
|
MOCK_METHOD2(OEMCrypto_GetDeviceID,
|
||||||
OEMCryptoResult(uint8_t* deviceID, size_t* idLength));
|
OEMCryptoResult(uint8_t* deviceID, size_t* idLength));
|
||||||
MOCK_METHOD2(OEMCrypto_CreateEntitledKeySession,
|
MOCK_METHOD2(OEMCrypto_CreateEntitledKeySession,
|
||||||
@@ -937,56 +925,35 @@ TEST_F(CryptoSessionTest, SelectKeys) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CryptoSessionTest, RefreshKeys) {
|
TEST_F(CryptoSessionTest, LoadRenewal) {
|
||||||
TestCryptoSession<StrictMock<MockedOEMCrypto> > crypto_session(
|
TestCryptoSession<NiceMock<MockedOEMCrypto> > crypto_session(
|
||||||
strict_oemcrypto_interface_);
|
nice_oemcrypto_interface_);
|
||||||
|
EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_))
|
||||||
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize());
|
|
||||||
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate());
|
|
||||||
EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_))
|
|
||||||
.WillOnce(
|
.WillOnce(
|
||||||
DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS)));
|
DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS)));
|
||||||
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
|
||||||
crypto_session.initialize().status_code());
|
crypto_session.initialize().status_code());
|
||||||
|
|
||||||
video_widevine::License license;
|
const std::string signed_message("signed_message");
|
||||||
|
const std::string core_message("core_message");
|
||||||
// Empty key array - no keys.
|
const std::string signature("signature");
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_CALL(
|
||||||
crypto_session
|
nice_oemcrypto_interface_,
|
||||||
.RefreshKeys(license.SerializeAsString(), "signature",
|
OEMCrypto_LoadRenewal(kOemcSessionId, NotNull(),
|
||||||
std::vector<wvcas::CryptoKey>())
|
signed_message.size() + core_message.size(),
|
||||||
.status_code());
|
core_message.size(), NotNull(), signature.size()))
|
||||||
|
|
||||||
auto* key_1 = license.add_key();
|
|
||||||
key_1->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
|
|
||||||
auto* key_2 = license.add_key();
|
|
||||||
key_2->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
|
|
||||||
auto* key_3 = license.add_key();
|
|
||||||
key_3->set_type(video_widevine::License_KeyContainer::KEY_CONTROL);
|
|
||||||
|
|
||||||
std::vector<wvcas::CryptoKey> key_array =
|
|
||||||
wvcas::ExtractKeyControlKeys(license);
|
|
||||||
|
|
||||||
EXPECT_CALL(strict_oemcrypto_interface_,
|
|
||||||
OEMCrypto_RefreshKeys(_, _, _, _, _, 3, _))
|
|
||||||
.WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE))
|
.WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE))
|
||||||
.WillOnce(Return(OEMCrypto_SUCCESS));
|
.WillOnce(Return(OEMCrypto_SUCCESS));
|
||||||
|
|
||||||
// OEMCrypto error.
|
// OEMCrypto error.
|
||||||
ASSERT_EQ(
|
ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError,
|
||||||
wvcas::CasStatusCode::kCryptoSessionError,
|
crypto_session.LoadRenewal(signed_message, core_message, signature)
|
||||||
crypto_session
|
.status_code());
|
||||||
.RefreshKeys(license.SerializeAsString(), "signature", key_array)
|
|
||||||
.status_code());
|
|
||||||
|
|
||||||
// Valid.
|
// Valid.
|
||||||
ASSERT_EQ(
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
wvcas::CasStatusCode::kNoError,
|
crypto_session.LoadRenewal(signed_message, core_message, signature)
|
||||||
crypto_session
|
.status_code());
|
||||||
.RefreshKeys(license.SerializeAsString(), "signature", key_array)
|
|
||||||
.status_code());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CryptoSessionTest, ReadUniqueId) {
|
TEST_F(CryptoSessionTest, ReadUniqueId) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class EcmParserV2Test : public testing::Test {
|
|||||||
void BuildEcm(bool with_rotation, bool content_iv_flag);
|
void BuildEcm(bool with_rotation, bool content_iv_flag);
|
||||||
|
|
||||||
std::vector<uint8_t> ecm_data_;
|
std::vector<uint8_t> ecm_data_;
|
||||||
std::unique_ptr<const wvcas::EcmParserV2> parser_;
|
std::unique_ptr<wvcas::EcmParserV2> parser_;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) {
|
size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
namespace wvcas {
|
namespace wvcas {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using video_widevine::EcmGroupKeyData;
|
||||||
using video_widevine::EcmMetaData;
|
using video_widevine::EcmMetaData;
|
||||||
using video_widevine::EcmPayload;
|
using video_widevine::EcmPayload;
|
||||||
using video_widevine::SignedEcmPayload;
|
using video_widevine::SignedEcmPayload;
|
||||||
@@ -84,7 +85,7 @@ TEST(EcmParserV3Test, CreateWithEvenKeySuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_EQ(parser->version(), kEcmVersion);
|
EXPECT_EQ(parser->version(), kEcmVersion);
|
||||||
@@ -121,7 +122,7 @@ TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_TRUE(parser->rotation_enabled());
|
EXPECT_TRUE(parser->rotation_enabled());
|
||||||
@@ -155,7 +156,7 @@ TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_TRUE(parser->rotation_enabled());
|
EXPECT_TRUE(parser->rotation_enabled());
|
||||||
@@ -187,7 +188,7 @@ TEST(EcmParserV3Test, AgeRestrictionSuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
||||||
@@ -206,7 +207,7 @@ TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
||||||
@@ -229,7 +230,7 @@ TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_EQ(parser->crypto_mode(), expected);
|
EXPECT_EQ(parser->crypto_mode(), expected);
|
||||||
@@ -252,7 +253,7 @@ TEST(EcmParserV3Test, FingerprintingSuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_TRUE(parser->has_fingerprinting());
|
EXPECT_TRUE(parser->has_fingerprinting());
|
||||||
@@ -267,7 +268,7 @@ TEST(EcmParserV3Test, ServiceBlockingSuccess) {
|
|||||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_TRUE(parser->has_service_blocking());
|
EXPECT_TRUE(parser->has_service_blocking());
|
||||||
@@ -281,11 +282,126 @@ TEST(EcmParserV3Test, SignatureSuccess) {
|
|||||||
signed_ecm_payload.set_signature(expected_signature);
|
signed_ecm_payload.set_signature(expected_signature);
|
||||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
std::unique_ptr<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
ASSERT_TRUE(parser != nullptr);
|
ASSERT_TRUE(parser != nullptr);
|
||||||
EXPECT_EQ(parser->signature(), expected_signature);
|
EXPECT_EQ(parser->signature(), expected_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(EcmParserV3Test, SetGroupIdSuccess) {
|
||||||
|
const std::string group_id = "group_id";
|
||||||
|
const std::string group_id2 = "another_group";
|
||||||
|
EcmPayload ecm_payload;
|
||||||
|
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||||
|
group_key_data->set_group_id(group_id);
|
||||||
|
group_key_data = ecm_payload.add_group_key_data();
|
||||||
|
group_key_data->set_group_id(group_id2);
|
||||||
|
SignedEcmPayload signed_ecm_payload;
|
||||||
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
|
ASSERT_TRUE(parser != nullptr);
|
||||||
|
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||||
|
EXPECT_TRUE(parser->set_group_id(group_id2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EcmParserV3Test, SetUnknownGroupIdFail) {
|
||||||
|
EcmPayload ecm_payload;
|
||||||
|
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||||
|
group_key_data->set_group_id("group_id");
|
||||||
|
SignedEcmPayload signed_ecm_payload;
|
||||||
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
|
ASSERT_TRUE(parser != nullptr);
|
||||||
|
EXPECT_FALSE(parser->set_group_id("unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EcmParserV3Test, ParserWithGroupIdSuccess) {
|
||||||
|
const std::string group_id = "group_id";
|
||||||
|
EcmPayload ecm_payload;
|
||||||
|
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||||
|
group_key_data->set_group_id(group_id);
|
||||||
|
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||||
|
kEntitlementId);
|
||||||
|
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||||
|
kWrappedContentKey);
|
||||||
|
group_key_data->mutable_even_key_data()->set_content_iv(kContentIv);
|
||||||
|
group_key_data->mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||||
|
kWrappedContentKey2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||||
|
|
||||||
|
SignedEcmPayload signed_ecm_payload;
|
||||||
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
|
||||||
|
ASSERT_TRUE(parser != nullptr);
|
||||||
|
// If group Id is not set, the normal keys will be returned.
|
||||||
|
std::vector<uint8_t> result =
|
||||||
|
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
|
||||||
|
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||||
|
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||||
|
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||||
|
|
||||||
|
// Now set the group id.
|
||||||
|
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||||
|
result = parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||||
|
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||||
|
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||||
|
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) {
|
||||||
|
const std::string group_id = "group_id";
|
||||||
|
EcmPayload ecm_payload;
|
||||||
|
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||||
|
group_key_data->set_group_id(group_id);
|
||||||
|
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||||
|
kEntitlementId);
|
||||||
|
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||||
|
kWrappedContentKey);
|
||||||
|
// Content IV and wrapped key iv is omitted in |group_key_data|/
|
||||||
|
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||||
|
kWrappedContentKey2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||||
|
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||||
|
SignedEcmPayload signed_ecm_payload;
|
||||||
|
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||||
|
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||||
|
|
||||||
|
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||||
|
ASSERT_TRUE(parser != nullptr);
|
||||||
|
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||||
|
|
||||||
|
std::vector<uint8_t> result =
|
||||||
|
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||||
|
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||||
|
// Content IV and wrapped key iv are from normal non-group key.
|
||||||
|
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||||
|
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||||
|
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace wvcas
|
} // namespace wvcas
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ TEST(IntegrationTests, TestCasPluginEventPassing) {
|
|||||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
|
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(IntegrationTests, TestSessionFailWithoutProvisioning) {
|
||||||
|
EXPECT_EQ(kIntegrationTestPassed,
|
||||||
|
RunNamedTest("TestSessionFailWithoutProvisioning"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(IntegrationTests, TestUniqueIdQuery) {
|
TEST(IntegrationTests, TestUniqueIdQuery) {
|
||||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
|
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::DoAll;
|
using ::testing::DoAll;
|
||||||
using ::testing::NiceMock;
|
using ::testing::NiceMock;
|
||||||
|
using ::testing::NotNull;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::SetArgPointee;
|
using ::testing::SetArgPointee;
|
||||||
using ::testing::StrictMock;
|
using ::testing::StrictMock;
|
||||||
@@ -54,20 +55,27 @@ class MockLicense : public wvcas::CasLicense {
|
|||||||
const std::string& wrapped_rsa_key,
|
const std::string& wrapped_rsa_key,
|
||||||
wvcas::LicenseType license_type,
|
wvcas::LicenseType license_type,
|
||||||
std::string* signed_license_request));
|
std::string* signed_license_request));
|
||||||
MOCK_METHOD2(HandleStoredLicense,
|
MOCK_METHOD3(HandleStoredLicense,
|
||||||
wvcas::CasStatus(const std::string& wrapped_rsa_key,
|
wvcas::CasStatus(const std::string& wrapped_rsa_key,
|
||||||
const std::string& license_file));
|
const std::string& license_file,
|
||||||
|
const std::string* content_id_filter));
|
||||||
MOCK_METHOD2(GenerateEntitlementRenewalRequest,
|
MOCK_METHOD2(GenerateEntitlementRenewalRequest,
|
||||||
wvcas::CasStatus(const std::string& device_certificate,
|
wvcas::CasStatus(const std::string& device_certificate,
|
||||||
std::string* signed_renewal_request));
|
std::string* signed_renewal_request));
|
||||||
MOCK_METHOD2(HandleEntitlementRenewalResponse,
|
MOCK_METHOD2(HandleEntitlementRenewalResponse,
|
||||||
wvcas::CasStatus(const std::string& renewal_response,
|
wvcas::CasStatus(const std::string& renewal_response,
|
||||||
std::string* device_file));
|
std::string* device_file));
|
||||||
MOCK_METHOD2(HandleEntitlementResponse,
|
MOCK_METHOD3(HandleEntitlementResponse,
|
||||||
wvcas::CasStatus(const std::string& entitlement_response,
|
wvcas::CasStatus(const std::string& entitlement_response,
|
||||||
|
const std::string* content_id_filter,
|
||||||
std::string* device_file));
|
std::string* device_file));
|
||||||
MOCK_METHOD0(BeginDecryption, void());
|
MOCK_METHOD0(BeginDecryption, void());
|
||||||
MOCK_METHOD0(UpdateLicenseForLicenseRemove, void());
|
MOCK_METHOD0(UpdateLicenseForLicenseRemove, void());
|
||||||
|
MOCK_METHOD(std::string, GetGroupId, (), (const, override));
|
||||||
|
MOCK_METHOD(std::vector<std::string>, GetContentIdList, (),
|
||||||
|
(const, override));
|
||||||
|
MOCK_METHOD(bool, IsMultiContentLicense, (), (const, override));
|
||||||
|
MOCK_METHOD(bool, IsGroupLicense, (), (const, override));
|
||||||
};
|
};
|
||||||
typedef StrictMock<MockLicense> StrictMockLicense;
|
typedef StrictMock<MockLicense> StrictMockLicense;
|
||||||
|
|
||||||
@@ -136,8 +144,10 @@ class MockWidevineSession : public wvcas::WidevineCasSession {
|
|||||||
public:
|
public:
|
||||||
MockWidevineSession() {}
|
MockWidevineSession() {}
|
||||||
~MockWidevineSession() override {}
|
~MockWidevineSession() override {}
|
||||||
MOCK_METHOD2(processEcm, wvcas::CasStatus(const wvcas::CasEcm& ecm,
|
MOCK_METHOD(wvcas::CasStatus, processEcm,
|
||||||
uint8_t parental_control_age));
|
(const wvcas::CasEcm& ecm, uint8_t parental_control_age,
|
||||||
|
const std::string& license_group_id),
|
||||||
|
(override));
|
||||||
MOCK_METHOD2(HandleProcessEcm,
|
MOCK_METHOD2(HandleProcessEcm,
|
||||||
wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId,
|
wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId,
|
||||||
const wvcas::CasEcm& ecm));
|
const wvcas::CasEcm& ecm));
|
||||||
@@ -238,6 +248,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
|
|||||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.initialize(nullptr).status_code());
|
cas_api.initialize(nullptr).status_code());
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
|
||||||
|
|
||||||
// Invalid parameter.
|
// Invalid parameter.
|
||||||
std::string request, init_data, license_id;
|
std::string request, init_data, license_id;
|
||||||
@@ -267,7 +278,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
|
|||||||
.WillRepeatedly(Return(mock_file));
|
.WillRepeatedly(Return(mock_file));
|
||||||
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||||
|
|
||||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _))
|
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
|
||||||
.WillRepeatedly(Return(wvcas::CasStatus(
|
.WillRepeatedly(Return(wvcas::CasStatus(
|
||||||
wvcas::CasStatusCode::kCasLicenseError, "forced failure")));
|
wvcas::CasStatusCode::kCasLicenseError, "forced failure")));
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError,
|
EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError,
|
||||||
@@ -281,7 +292,7 @@ TEST_F(WidevineCasTest, generateEntitlementRequest) {
|
|||||||
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize));
|
||||||
// For expired license file, remove it successfully
|
// For expired license file, remove it successfully
|
||||||
// and return CasLicenseError.
|
// and return CasLicenseError.
|
||||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _))
|
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
|
||||||
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true));
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true));
|
||||||
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
|
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
|
||||||
@@ -442,6 +453,7 @@ TEST_P(WidevineCasTest, ECMProcessing) {
|
|||||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.initialize(nullptr).status_code());
|
cas_api.initialize(nullptr).status_code());
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
|
||||||
|
|
||||||
wvcas::WvCasSessionId video_sid;
|
wvcas::WvCasSessionId video_sid;
|
||||||
wvcas::WvCasSessionId audio_sid;
|
wvcas::WvCasSessionId audio_sid;
|
||||||
@@ -487,10 +499,10 @@ TEST_P(WidevineCasTest, ECMProcessing) {
|
|||||||
int expected_begin_decryption_calls = expected_process_ecm_calls * 2;
|
int expected_begin_decryption_calls = expected_process_ecm_calls * 2;
|
||||||
|
|
||||||
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(video_session.get()),
|
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(video_session.get()),
|
||||||
processEcm(video_ecm, 0))
|
processEcm(video_ecm, 0, ""))
|
||||||
.Times(expected_process_ecm_calls);
|
.Times(expected_process_ecm_calls);
|
||||||
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(audio_session.get()),
|
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(audio_session.get()),
|
||||||
processEcm(audio_ecm, 0))
|
processEcm(audio_ecm, 0, ""))
|
||||||
.Times(expected_process_ecm_calls);
|
.Times(expected_process_ecm_calls);
|
||||||
EXPECT_CALL(*cas_api.license_, BeginDecryption())
|
EXPECT_CALL(*cas_api.license_, BeginDecryption())
|
||||||
.Times(expected_begin_decryption_calls);
|
.Times(expected_begin_decryption_calls);
|
||||||
@@ -507,7 +519,7 @@ TEST_P(WidevineCasTest, ECMProcessing) {
|
|||||||
.WillOnce(Return(file_handle));
|
.WillOnce(Return(file_handle));
|
||||||
|
|
||||||
EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file_data.size()));
|
EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file_data.size()));
|
||||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _));
|
EXPECT_CALL(*cas_api.license_, HandleStoredLicense);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
wvcas::CasStatusCode::kNoError,
|
wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.generateEntitlementRequest("init_data", &request, license_id)
|
cas_api.generateEntitlementRequest("init_data", &request, license_id)
|
||||||
@@ -520,12 +532,26 @@ TEST_P(WidevineCasTest, ECMProcessing) {
|
|||||||
license_id);
|
license_id);
|
||||||
} else {
|
} else {
|
||||||
// Empty response.
|
// Empty response.
|
||||||
std::string init_data;
|
// Initialize CaMediaId.
|
||||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _))
|
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false));
|
||||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _))
|
||||||
|
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
std::string request, init_data, license_id;
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
wvcas::CasStatusCode::kNoError,
|
wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.handleEntitlementResponse("response", init_data).status_code());
|
cas_api.generateEntitlementRequest(init_data, &request, license_id)
|
||||||
|
.status_code());
|
||||||
|
|
||||||
|
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse)
|
||||||
|
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||||
|
std::string multi_content_license_info;
|
||||||
|
std::string group_license_info;
|
||||||
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
|
cas_api
|
||||||
|
.handleEntitlementResponse("response", init_data,
|
||||||
|
multi_content_license_info,
|
||||||
|
group_license_info)
|
||||||
|
.status_code());
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
@@ -589,17 +615,217 @@ TEST_F(WidevineCasTest, RemoveLicense) {
|
|||||||
|
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.RemoveLicense(mocked_file_name).status_code());
|
cas_api.RemoveLicense(mocked_file_name).status_code());
|
||||||
|
}
|
||||||
|
|
||||||
// Happy case: remove the in used license file
|
TEST_F(WidevineCasTest, RemoveLicenseInUse) {
|
||||||
std::string hash;
|
TestWidevineCas cas_api;
|
||||||
std::string kBasePathPrefix = "/data/vendor/mediacas/IDM/widevine/";
|
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||||
hash.resize(SHA256_DIGEST_LENGTH);
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
const auto* input =
|
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||||
reinterpret_cast<const unsigned char*>(mocked_file_name.data());
|
wvcas::CasStatusCode::kNoError);
|
||||||
auto* output = reinterpret_cast<unsigned char*>(&hash[0]);
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||||
SHA256(input, mocked_file_name.size(), output);
|
|
||||||
std::string full_filename = GenerateTestLicenseFileName(mocked_file_name);
|
// Initialize media_id_.
|
||||||
|
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
std::string request, init_data, license_id;
|
||||||
|
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
// Install the license
|
||||||
|
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||||
|
Return(wvcas::CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||||
|
.WillRepeatedly(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetGroupId).WillRepeatedly(Return(""));
|
||||||
|
std::string multi_content_license_info;
|
||||||
|
std::string group_license_info;
|
||||||
|
EXPECT_EQ(cas_api
|
||||||
|
.handleEntitlementResponse("response", license_id,
|
||||||
|
multi_content_license_info,
|
||||||
|
group_license_info)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
EXPECT_FALSE(license_id.empty());
|
||||||
|
|
||||||
|
MockFile mock_file;
|
||||||
|
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true));
|
||||||
EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1);
|
EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1);
|
||||||
|
EXPECT_EQ(cas_api.RemoveLicense(license_id + ".lic").status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WidevineCasTest, handleMultiContentEntitlementResponse) {
|
||||||
|
TestWidevineCas cas_api;
|
||||||
|
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||||
|
|
||||||
|
// Initialize media_id_.
|
||||||
|
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
std::string request, init_data, license_id;
|
||||||
|
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
|
||||||
|
std::string license_group_id = "license_group_id";
|
||||||
|
std::vector<std::string> content_list = {"content1", "content2"};
|
||||||
|
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||||
|
Return(wvcas::CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||||
|
.WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetGroupId)
|
||||||
|
.WillRepeatedly(Return(license_group_id));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetContentIdList)
|
||||||
|
.WillRepeatedly(Return(content_list));
|
||||||
|
|
||||||
|
std::string multi_content_license_info;
|
||||||
|
std::string group_license_info;
|
||||||
|
EXPECT_EQ(cas_api
|
||||||
|
.handleEntitlementResponse("response", license_id,
|
||||||
|
multi_content_license_info,
|
||||||
|
group_license_info)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
|
||||||
|
std::string expected_license_id =
|
||||||
|
GenerateTestLicenseFileName(license_group_id);
|
||||||
|
expected_license_id = expected_license_id.substr(
|
||||||
|
0, expected_license_id.size() - strlen(kLicenseFileNameSuffix));
|
||||||
|
EXPECT_EQ(license_id, expected_license_id);
|
||||||
|
|
||||||
|
std::string expected_info;
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(license_id.size());
|
||||||
|
expected_info.append(license_id);
|
||||||
|
expected_info.push_back(1);
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(content_list[0].size());
|
||||||
|
expected_info.append(content_list[0]);
|
||||||
|
expected_info.push_back(1);
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(content_list[1].size());
|
||||||
|
expected_info.append(content_list[1]);
|
||||||
|
EXPECT_EQ(multi_content_license_info, expected_info);
|
||||||
|
EXPECT_TRUE(group_license_info.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WidevineCasTest, handleGroupEntitlementResponse) {
|
||||||
|
TestWidevineCas cas_api;
|
||||||
|
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||||
|
|
||||||
|
// Initialize media_id_.
|
||||||
|
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
std::string request, init_data, license_id;
|
||||||
|
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
|
||||||
|
std::string license_group_id = "license_group_id";
|
||||||
|
std::vector<std::string> content_list = {};
|
||||||
|
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||||
|
Return(wvcas::CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||||
|
.WillRepeatedly(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetGroupId)
|
||||||
|
.WillRepeatedly(Return(license_group_id));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetContentIdList)
|
||||||
|
.WillRepeatedly(Return(content_list));
|
||||||
|
|
||||||
|
std::string multi_content_license_info;
|
||||||
|
std::string group_license_info;
|
||||||
|
EXPECT_EQ(cas_api
|
||||||
|
.handleEntitlementResponse("response", license_id,
|
||||||
|
multi_content_license_info,
|
||||||
|
group_license_info)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
|
||||||
|
std::string expected_license_id =
|
||||||
|
GenerateTestLicenseFileName(license_group_id);
|
||||||
|
expected_license_id = expected_license_id.substr(
|
||||||
|
0, expected_license_id.size() - strlen(kLicenseFileNameSuffix));
|
||||||
|
EXPECT_EQ(license_id, expected_license_id);
|
||||||
|
|
||||||
|
std::string expected_info;
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(license_id.size());
|
||||||
|
expected_info.append(license_id);
|
||||||
|
expected_info.push_back(1);
|
||||||
|
expected_info.push_back(0);
|
||||||
|
expected_info.push_back(license_group_id.size());
|
||||||
|
expected_info.append(license_group_id);
|
||||||
|
EXPECT_EQ(group_license_info, expected_info);
|
||||||
|
EXPECT_TRUE(multi_content_license_info.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(WidevineCasTest, ECMProcessingWithGroupId) {
|
||||||
|
TestWidevineCas cas_api;
|
||||||
|
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||||
|
|
||||||
|
// Initialize media_id_.
|
||||||
|
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest)
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
std::string request, init_data, license_id;
|
||||||
|
EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
// Install license
|
||||||
|
const std::string license_group_id = "license_group_id";
|
||||||
|
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse)
|
||||||
|
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||||
|
.WillRepeatedly(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(*cas_api.license_, GetGroupId)
|
||||||
|
.WillRepeatedly(Return(license_group_id));
|
||||||
|
std::string multi_content_license_info;
|
||||||
|
std::string group_license_info;
|
||||||
|
EXPECT_EQ(cas_api
|
||||||
|
.handleEntitlementResponse("response", license_id,
|
||||||
|
multi_content_license_info,
|
||||||
|
group_license_info)
|
||||||
|
.status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
// Init a session
|
||||||
|
wvcas::WvCasSessionId sid;
|
||||||
|
EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_))
|
||||||
|
.WillOnce(
|
||||||
|
DoAll(SetArgPointee<0>(10), Return(wvcas::CasStatus::OkStatus())));
|
||||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
cas_api.RemoveLicense(full_filename).status_code());
|
cas_api.openSession(&sid).status_code());
|
||||||
|
wvcas::CasSessionPtr session =
|
||||||
|
wvcas::WidevineCasSessionMap::instance().GetSession(sid);
|
||||||
|
ASSERT_TRUE(session != nullptr);
|
||||||
|
const wvcas::CasEcm ecm = {1, 2, 3};
|
||||||
|
// It is expected that process ecm with group_id
|
||||||
|
EXPECT_CALL(*reinterpret_cast<MockWidevineSession*>(session.get()),
|
||||||
|
processEcm(ecm, 0, license_group_id))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(*cas_api.license_, BeginDecryption());
|
||||||
|
|
||||||
|
EXPECT_EQ(cas_api.processEcm(sid, ecm).status_code(),
|
||||||
|
wvcas::CasStatusCode::kNoError);
|
||||||
|
EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(sid));
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,7 @@ static const char kOddWrappedKeyIv[] = "odd_wrapped_content_key_iv";
|
|||||||
static const char kEvenContentIv[] = "even_content_iv";
|
static const char kEvenContentIv[] = "even_content_iv";
|
||||||
static const char kOddContentIv[] = "odd_content_iv";
|
static const char kOddContentIv[] = "odd_content_iv";
|
||||||
static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111;
|
static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111;
|
||||||
|
constexpr char kEmptyGroupId[] = "";
|
||||||
|
|
||||||
MATCHER(IsValidKeyEvenSlotData, "") {
|
MATCHER(IsValidKeyEvenSlotData, "") {
|
||||||
if (nullptr == arg) {
|
if (nullptr == arg) {
|
||||||
@@ -125,6 +126,7 @@ class MockEcmParser : public wvcas::EcmParser {
|
|||||||
std::vector<uint8_t>(wvcas::KeySlotId id));
|
std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||||
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||||
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||||
|
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
|
||||||
MOCK_CONST_METHOD0(has_fingerprinting, bool());
|
MOCK_CONST_METHOD0(has_fingerprinting, bool());
|
||||||
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
|
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
|
||||||
MOCK_CONST_METHOD0(has_service_blocking, bool());
|
MOCK_CONST_METHOD0(has_service_blocking, bool());
|
||||||
@@ -145,7 +147,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
|
|||||||
TestCasSession() {}
|
TestCasSession() {}
|
||||||
virtual ~TestCasSession() {}
|
virtual ~TestCasSession() {}
|
||||||
|
|
||||||
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
|
std::unique_ptr<wvcas::EcmParser> getEcmParser(
|
||||||
const wvcas::CasEcm& ecm) const override;
|
const wvcas::CasEcm& ecm) const override;
|
||||||
|
|
||||||
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
|
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
|
||||||
@@ -222,7 +224,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
|
|||||||
video_widevine::ServiceBlocking service_blocking_;
|
video_widevine::ServiceBlocking service_blocking_;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
|
std::unique_ptr<wvcas::EcmParser> TestCasSession::getEcmParser(
|
||||||
const wvcas::CasEcm& ecm) const {
|
const wvcas::CasEcm& ecm) const {
|
||||||
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
|
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
|
||||||
new NiceMock<MockEcmParser>);
|
new NiceMock<MockEcmParser>);
|
||||||
@@ -241,6 +243,7 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
|
|||||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
|
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
|
||||||
ON_CALL(*mock_ecm_parser, content_iv(_))
|
ON_CALL(*mock_ecm_parser, content_iv(_))
|
||||||
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
|
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
|
||||||
|
ON_CALL(*mock_ecm_parser, set_group_id(_)).WillByDefault(Return(true));
|
||||||
ON_CALL(*mock_ecm_parser, has_fingerprinting())
|
ON_CALL(*mock_ecm_parser, has_fingerprinting())
|
||||||
.WillByDefault(Return(fingerprinting_.has_control()));
|
.WillByDefault(Return(fingerprinting_.has_control()));
|
||||||
ON_CALL(*mock_ecm_parser, fingerprinting())
|
ON_CALL(*mock_ecm_parser, fingerprinting())
|
||||||
@@ -249,7 +252,7 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
|
|||||||
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
|
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
|
||||||
ON_CALL(*mock_ecm_parser, service_blocking())
|
ON_CALL(*mock_ecm_parser, service_blocking())
|
||||||
.WillByDefault(Return(service_blocking_));
|
.WillByDefault(Return(service_blocking_));
|
||||||
return std::unique_ptr<const wvcas::EcmParser>(mock_ecm_parser.release());
|
return std::unique_ptr<wvcas::EcmParser>(mock_ecm_parser.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, processEcm) {
|
TEST_F(CasSessionTest, processEcm) {
|
||||||
@@ -269,7 +272,7 @@ TEST_F(CasSessionTest, processEcm) {
|
|||||||
wvcas::CasEcm ecm(184);
|
wvcas::CasEcm ecm(184);
|
||||||
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
|
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
|
||||||
IsValidKeyOddSlotData()));
|
IsValidKeyOddSlotData()));
|
||||||
session.processEcm(ecm, 0);
|
session.processEcm(ecm, 0, kEmptyGroupId);
|
||||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,25 +296,25 @@ TEST_F(CasSessionTest, parentalControl) {
|
|||||||
// Different Ecm to make sure processEcm() processes this ecm.
|
// Different Ecm to make sure processEcm() processes this ecm.
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
session.processEcm(ecm, 0).status_code());
|
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
session.processEcm(ecm, 13).status_code());
|
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||||
|
|
||||||
// Parental control age must >= 10 (if non-zero).
|
// Parental control age must >= 10 (if non-zero).
|
||||||
session.set_age_restriction(10);
|
session.set_age_restriction(10);
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
session.processEcm(ecm, 0).status_code());
|
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
session.processEcm(ecm, 10).status_code());
|
session.processEcm(ecm, 10, kEmptyGroupId).status_code());
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||||
session.processEcm(ecm, 13).status_code());
|
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||||
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
|
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
|
||||||
session.processEcm(ecm, 3).status_code());
|
session.processEcm(ecm, 3, kEmptyGroupId).status_code());
|
||||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +357,7 @@ TEST_F(CasSessionTest, FingerprintingSuccess) {
|
|||||||
OnSessionFingerprintingUpdated(session_id, expected_message))
|
OnSessionFingerprintingUpdated(session_id, expected_message))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
||||||
@@ -368,10 +371,10 @@ TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
|||||||
session.set_fingerprinting_control("control");
|
session.set_fingerprinting_control("control");
|
||||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
// Same fingerprinting will not trigger event.
|
// Same fingerprinting will not trigger event.
|
||||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
|
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
|
||||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
||||||
@@ -385,15 +388,15 @@ TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
|||||||
session.set_fingerprinting_control("control");
|
session.set_fingerprinting_control("control");
|
||||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
// Different fingerprinting will trigger event.
|
// Different fingerprinting will trigger event.
|
||||||
session.set_fingerprinting_control("control2");
|
session.set_fingerprinting_control("control2");
|
||||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||||
// Different fingerprinting (including empty) will trigger event.
|
// Different fingerprinting (including empty) will trigger event.
|
||||||
session.set_fingerprinting_control("");
|
session.set_fingerprinting_control("");
|
||||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||||
session.processEcm(wvcas::CasEcm(184, '2'), 0);
|
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
||||||
@@ -412,7 +415,7 @@ TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
|||||||
OnSessionServiceBlockingUpdated(session_id, expected_message))
|
OnSessionServiceBlockingUpdated(session_id, expected_message))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
||||||
@@ -426,9 +429,9 @@ TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
|||||||
session.set_service_blocking_groups({"Group1", "g2"});
|
session.set_service_blocking_groups({"Group1", "g2"});
|
||||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
|
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
|
||||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
||||||
@@ -442,13 +445,13 @@ TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
|||||||
session.set_service_blocking_groups({"Group1", "g2"});
|
session.set_service_blocking_groups({"Group1", "g2"});
|
||||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||||
|
|
||||||
session.processEcm(wvcas::CasEcm(184, '0'), 0);
|
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||||
session.set_service_blocking_groups({"Group1"});
|
session.set_service_blocking_groups({"Group1"});
|
||||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||||
session.set_service_blocking_groups({});
|
session.set_service_blocking_groups({});
|
||||||
session.processEcm(wvcas::CasEcm(184, '2'), 0);
|
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
213
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
213
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||||
|
// source code may only be used and distributed under the Widevine Master
|
||||||
|
// License Agreement.
|
||||||
|
|
||||||
|
#include "widevine_media_cas_plugin.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <utils/String8.h>
|
||||||
|
|
||||||
|
#include "cas_events.h"
|
||||||
|
#include "cas_status.h"
|
||||||
|
#include "media/cas/CasAPI.h"
|
||||||
|
#include "media/stagefright/MediaErrors.h"
|
||||||
|
#include "widevine_cas_api.h"
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
// Minimalist implementation of Android string class to support test.
|
||||||
|
std::map<const String8*, std::unique_ptr<std::string> > string8s;
|
||||||
|
|
||||||
|
String8::String8(const String8& value)
|
||||||
|
: String8(value.c_str(), value.length()) {}
|
||||||
|
|
||||||
|
String8::String8(char const* data, size_t data_length) {
|
||||||
|
auto result =
|
||||||
|
string8s.emplace(this, make_unique<std::string>(data, data_length));
|
||||||
|
mString = result.first->second->data();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t String8::length() const {
|
||||||
|
auto it = string8s.find(this);
|
||||||
|
return it == string8s.end() ? 0 : it->second->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
String8::~String8() { string8s.erase(this); }
|
||||||
|
|
||||||
|
} // namespace android
|
||||||
|
|
||||||
|
namespace wvcas {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::DoAll;
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::IsNull;
|
||||||
|
using ::testing::NotNull;
|
||||||
|
using ::testing::Return;
|
||||||
|
using ::testing::SetArgPointee;
|
||||||
|
using ::testing::SetArgReferee;
|
||||||
|
|
||||||
|
class MockWidevineCas : public WidevineCas {
|
||||||
|
public:
|
||||||
|
MockWidevineCas() {}
|
||||||
|
~MockWidevineCas() override {}
|
||||||
|
|
||||||
|
MOCK_METHOD(CasStatus, openSession, (WvCasSessionId * sessionId), (override));
|
||||||
|
MOCK_METHOD(bool, is_provisioned, (), (const, override));
|
||||||
|
MOCK_METHOD(CasStatus, generateEntitlementRequest,
|
||||||
|
(const std::string& init_data, std::string* entitlement_request,
|
||||||
|
std::string& license_id),
|
||||||
|
(override));
|
||||||
|
MOCK_METHOD(CasStatus, RecordLicenseId, (const std::string& license_id),
|
||||||
|
(override));
|
||||||
|
MOCK_METHOD(CasStatus, handleEntitlementResponse,
|
||||||
|
(const std::string& response, std::string& license_id,
|
||||||
|
std::string& multi_content_license_info,
|
||||||
|
std::string& group_license_info),
|
||||||
|
(override));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override WidevineCasPlugin to set WidevineCas and mock callbacks.
|
||||||
|
class TestWidevineCasPlugin : public WidevineCasPlugin {
|
||||||
|
public:
|
||||||
|
TestWidevineCasPlugin() : WidevineCasPlugin() {}
|
||||||
|
~TestWidevineCasPlugin() override {}
|
||||||
|
|
||||||
|
void SetWidevineCasApi(
|
||||||
|
std::unique_ptr<WidevineCas> widevine_cas_api) override {
|
||||||
|
WidevineCasPlugin::SetWidevineCasApi(std::move(widevine_cas_api));
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD(void, CallBack,
|
||||||
|
(void* appData, int32_t event, int32_t arg, uint8_t* data,
|
||||||
|
size_t size, const CasSessionId* sessionId),
|
||||||
|
(const, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, openSessionSuccess) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
const int32_t created_session_id = 0x12345678;
|
||||||
|
const std::vector<uint8_t> expected_android_session_id = {0x78, 0x56, 0x34,
|
||||||
|
0x12};
|
||||||
|
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(*cas_api, openSession(NotNull()))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<0>(created_session_id),
|
||||||
|
Return(CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, CAS_SESSION_ID, created_session_id, IsNull(),
|
||||||
|
0, IsNull()));
|
||||||
|
std::vector<uint8_t> session_id;
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin.openSession(&session_id), android::OK);
|
||||||
|
|
||||||
|
EXPECT_EQ(session_id, expected_android_session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, openSessionWithoutProvisionFail) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(false));
|
||||||
|
EXPECT_CALL(*cas_api, openSession(NotNull())).Times((0));
|
||||||
|
std::vector<uint8_t> session_id;
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin.openSession(&session_id),
|
||||||
|
android::ERROR_CAS_NOT_PROVISIONED);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest,
|
||||||
|
provisionWithProvisionStringAlreadyProvisionedSuccess) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
const std::string provision_string = "init_data";
|
||||||
|
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, INDIVIDUALIZATION_COMPLETE, _, _, _, _));
|
||||||
|
// Provision string is init data; it triggers license request.
|
||||||
|
EXPECT_CALL(*cas_api,
|
||||||
|
generateEntitlementRequest(Eq(provision_string), NotNull(), _))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<1>("signed_license_request"),
|
||||||
|
Return(CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, LICENSE_REQUEST, _, _, _, _));
|
||||||
|
|
||||||
|
const android::String8 provision_msg();
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin.provision(android::String8(provision_string.c_str(),
|
||||||
|
provision_string.size())),
|
||||||
|
android::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, HandleAssignLicenseIDSuccess) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
const std::string license_id = "license_id";
|
||||||
|
EXPECT_CALL(*cas_api, RecordLicenseId(license_id))
|
||||||
|
.WillOnce(Return(CasStatus::OkStatus()));
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, LICENSE_ID_ASSIGNED, _, _, _, _)).Times(1);
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||||
|
{license_id.begin(), license_id.end()}),
|
||||||
|
android::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, HandleAssignLicenseIDApiError) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
const std::string license_id = "license_id";
|
||||||
|
EXPECT_CALL(*cas_api, RecordLicenseId)
|
||||||
|
.WillOnce(Return(CasStatus(CasStatusCode::kInvalidParameter, "invalid")));
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||||
|
|
||||||
|
EXPECT_NE(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||||
|
{license_id.begin(), license_id.end()}),
|
||||||
|
android::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, HandleEntitlementResponseSuccess) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||||
|
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||||
|
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||||
|
const std::string license = "license";
|
||||||
|
const std::string license_id = "id";
|
||||||
|
const std::string multi_content_license_info = "info";
|
||||||
|
const std::string group_license_info = "info2";
|
||||||
|
EXPECT_CALL(*cas_api, handleEntitlementResponse(_, _, _, _))
|
||||||
|
.WillOnce(DoAll(SetArgReferee<1>(license_id),
|
||||||
|
SetArgReferee<2>(multi_content_license_info),
|
||||||
|
SetArgReferee<3>(group_license_info),
|
||||||
|
Return(CasStatus::OkStatus())));
|
||||||
|
EXPECT_CALL(plugin,
|
||||||
|
CallBack(_, LICENSE_CAS_READY, _, _, license_id.size(), _))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, MULTI_CONTENT_LICENSE_INFO, _, _,
|
||||||
|
multi_content_license_info.size(), _))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, GROUP_LICENSE_INFO, _, _,
|
||||||
|
group_license_info.size(), _))
|
||||||
|
.Times(1);
|
||||||
|
|
||||||
|
EXPECT_EQ(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0,
|
||||||
|
{license.begin(), license.end()}),
|
||||||
|
android::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) {
|
||||||
|
TestWidevineCasPlugin plugin;
|
||||||
|
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||||
|
|
||||||
|
EXPECT_NE(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, /*eventData=*/{}),
|
||||||
|
android::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace wvcas
|
||||||
Reference in New Issue
Block a user