From 1b295f4c8114c9bd0da4a6e62220633195884df9 Mon Sep 17 00:00:00 2001 From: Jeff Tinker Date: Wed, 15 May 2013 19:23:36 -0700 Subject: [PATCH] Support Offline Licenses Bug: 8621588 Merge of the following CLs from the Widevine CDM repository: https://widevine-internal-review.googlesource.com/#/c/5602/ https://widevine-internal-review.googlesource.com/#/c/5431/ https://widevine-internal-review.googlesource.com/#/c/5660/ Change-Id: If37940e2535e1a1eca95e4394d8cf9bf689e9c3a --- libwvdrmengine/cdm/core/include/cdm_engine.h | 19 +- libwvdrmengine/cdm/core/include/cdm_session.h | 36 +- .../cdm/core/include/device_files.h | 26 + libwvdrmengine/cdm/core/include/license.h | 14 +- .../cdm/core/include/wv_cdm_constants.h | 7 + .../cdm/core/include/wv_cdm_event_listener.h | 7 + .../cdm/core/include/wv_cdm_types.h | 4 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 230 +++++++-- libwvdrmengine/cdm/core/src/cdm_session.cpp | 209 +++++++- libwvdrmengine/cdm/core/src/device_files.cpp | 149 ++++++ .../cdm/core/src/device_files.proto | 12 +- libwvdrmengine/cdm/core/src/license.cpp | 137 ++++- .../cdm/core/test/cdm_engine_test.cpp | 20 +- .../cdm/core/test/device_files_unittest.cpp | 467 ++++++++++++++++++ .../cdm/core/test/license_request.cpp | 4 + libwvdrmengine/cdm/core/test/url_request.cpp | 29 +- libwvdrmengine/cdm/core/test/url_request.h | 1 + .../include/wv_content_decryption_module.h | 8 +- .../cdm/src/wv_content_decryption_module.cpp | 38 +- .../cdm/test/request_license_test.cpp | 142 ++++-- libwvdrmengine/level3/arm/libwvlevel3.a | Bin 293070 -> 293066 bytes libwvdrmengine/level3/mips/libwvlevel3.a | Bin 312138 -> 312154 bytes libwvdrmengine/level3/x86/libwvlevel3.a | Bin 304836 -> 304956 bytes .../mediacrypto/test/WVCryptoPlugin_test.cpp | 6 +- libwvdrmengine/mediadrm/include/WVDrmPlugin.h | 6 +- libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 75 ++- .../mediadrm/test/WVDrmPlugin_test.cpp | 206 +++++--- .../mock/src/oemcrypto_engine_mock.cpp | 23 +- .../oemcrypto/test/oemcrypto_keybox_test.cpp | 175 ------- .../oemcrypto/test/oemcrypto_test.cpp | 68 ++- 30 files changed, 1647 insertions(+), 471 deletions(-) delete mode 100644 libwvdrmengine/oemcrypto/test/oemcrypto_keybox_test.cpp diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index ae58eaaf..7e698d1a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -14,6 +14,7 @@ class CryptoEngine; class WvCdmEventListener; typedef std::map CdmSessionMap; +typedef std::map CdmReleaseKeySetMap; class CdmEngine : public TimerHandler { public: @@ -24,12 +25,13 @@ class CdmEngine : public TimerHandler { CdmResponseType OpenSession(const CdmKeySystem& key_system, CdmSessionId* session_id); CdmResponseType CloseSession(const CdmSessionId& session_id); + CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id); + CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id); // License related methods // Construct a valid license request CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, - bool is_key_system_present, - const CdmKeySystem& key_system, + const CdmKeySetId& key_set_id, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, @@ -38,12 +40,14 @@ class CdmEngine : public TimerHandler { // Accept license response and extract key info. CdmResponseType AddKey(const CdmSessionId& session_id, - const CdmKeyResponse& key_data); + const CdmKeyResponse& key_data, + CdmKeySetId& key_set_id); + + CdmResponseType RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id); // Cancel session and unload keys. - CdmResponseType CancelKeyRequest(const CdmSessionId& session_id, - bool is_key_system_present, - const CdmKeySystem& key_system); + CdmResponseType CancelKeyRequest(const CdmSessionId& session_id); // Construct valid renewal request for the current session keys. CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id, @@ -121,9 +125,12 @@ class CdmEngine : public TimerHandler { void DisablePolicyTimer(); virtual void OnTimerEvent(); + virtual void OnKeyReleaseEvent(CdmKeySetId key_set_id); + // instance variables CdmSession* provisioning_session_; CdmSessionMap sessions_; + CdmReleaseKeySetMap release_key_sets_; // policy timer Timer policy_timer_; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index e47a594c..e50cbf5a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -19,14 +19,16 @@ namespace wvcdm { class CdmSession { public: CdmSession() : session_id_(GenerateSessionId()), license_received_(false), - reinitialize_session_(false) {} + reinitialize_session_(false), license_type_(kLicenseTypeStreaming) {} ~CdmSession() {} CdmResponseType Init(); CdmResponseType ReInit(); - bool DestroySession(); + CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id, + const CdmLicenseType license_type); + void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; } const CdmKeySystem& key_system() { return key_system_; } @@ -37,12 +39,13 @@ class CdmSession { CdmResponseType GenerateKeyRequest(const CdmInitData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, + const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, std::string* server_url); // AddKey() - Accept license response and extract key info. - CdmResponseType AddKey(const CdmKeyResponse& key_response); + CdmResponseType AddKey(const CdmKeyResponse& key_response, + CdmKeySetId* key_set_id); // CancelKeyRequest() - Cancel session. CdmResponseType CancelKeyRequest(); @@ -74,19 +77,31 @@ class CdmSession { // RenewKey() - Accept renewal response and update key info. CdmResponseType RenewKey(const CdmKeyResponse& key_response); + // License release + // GenerateReleaseRequest() - Construct valid release request for the current + // session keys. + CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request, + std::string* server_url); + + // RenewKey() - Accept renewal response and update key info. + CdmResponseType ReleaseKey(const CdmKeyResponse& key_response); + bool IsKeyValid(const KeyId& key_id); bool AttachEventListener(WvCdmEventListener* listener); bool DetachEventListener(WvCdmEventListener* listener); void OnTimerEvent(); + void OnKeyReleaseEvent(CdmKeySetId key_set_id); private: // Generate unique ID for each new session. CdmSessionId GenerateSessionId(); + CdmKeySetId GenerateKeySetId(CdmInitData& pssh_data); bool LoadDeviceCertificate(std::string* cert, std::string* wrapped_key); + bool StoreLicense(bool active); // instance variables const CdmSessionId session_id_; @@ -97,6 +112,19 @@ class CdmSession { bool license_received_; bool reinitialize_session_; + CdmLicenseType license_type_; + + // license type offline related information + CdmInitData offline_pssh_data_; + CdmKeyMessage offline_key_request_; + CdmKeyResponse offline_key_response_; + CdmKeyMessage offline_key_renewal_request_; + CdmKeyResponse offline_key_renewal_response_; + std::string offline_release_server_url_; + + // license type release and offline related information + CdmKeySetId key_set_id_; + KeyId key_id_; // Used for certificate based licensing diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index d9294ebd..fa67e144 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -9,15 +9,41 @@ namespace wvcdm { class DeviceFiles { public: + typedef enum { + kLicenseStateActive, + kLicenseStateReleasing, + kLicenseStateUnknown, + } LicenseState; + static bool StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key); static bool RetrieveCertificate(std::string* certificate, std::string* wrapped_private_key); + static bool StoreLicense(const std::string& key_set_id, + const LicenseState state, + const CdmInitData& pssh_data, + const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response, + const CdmKeyMessage& key_renewal_request, + const CdmKeyResponse& key_renewal_response, + const std::string& release_server_url); + static bool RetrieveLicense(const std::string& key_set_id, + LicenseState* state, + CdmInitData* pssh_data, + CdmKeyMessage* key_request, + CdmKeyResponse* key_response, + CdmKeyMessage* key_renewal_request, + CdmKeyResponse* key_renewal_response, + std::string* release_server_url); + static bool DeleteLicense(const std::string& key_set_id); + static bool LicenseExists(const std::string& key_set_id); + static std::string GetBasePath(const char* dir); static const char* kBasePath; static const char* kPathDelimiter; static const char* kDeviceCertificateFileName; + static const char* kLicenseFileNameExt; private: static bool Hash(const std::string& data, std::string* hash); diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 4899c6b3..40713731 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -26,15 +26,21 @@ class CdmLicense { bool PrepareKeyRequest(const CdmInitData& pssh_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, + const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, std::string* server_url); - bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, - std::string* server_url); + bool PrepareKeyUpdateRequest(bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url); CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); - CdmResponseType HandleKeyRenewalResponse( + CdmResponseType HandleKeyUpdateResponse( + bool is_renewal, const CdmKeyResponse& license_response); + bool RestoreOfflineLicense(CdmKeyMessage& license_request, + CdmKeyResponse& license_response, + CdmKeyResponse& license_renewal_response); + private: CdmResponseType HandleKeyErrorResponse(const SignedMessage& signed_message); diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index 67d1b88b..6253a845 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -5,6 +5,8 @@ #include +#include "wv_cdm_types.h" + namespace wvcdm { static const size_t KEY_CONTROL_SIZE = 16; // TODO(kqyang): Key ID size is not fixed in spec, but conventionally we @@ -16,6 +18,11 @@ static const size_t KEY_PAD_SIZE = 16; static const size_t KEY_SIZE = 16; static const size_t MAC_KEY_SIZE = 32; +static const std::string SESSION_ID_PREFIX = "sid"; +static const std::string KEY_SET_ID_PREFIX = "ksid"; + +static const CdmKeySystem KEY_SYSTEM = "com.widevine"; + // define query keys, values here static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType"; // "Streaming", "Offline" diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h b/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h index b9bfb476..a8136709 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h @@ -11,15 +11,22 @@ namespace wvcdm { // The caller of the CDM API must provide an implementation for onEvent // and signal its intent by using the Attach/DetachEventListener methods // in the WvContentDecryptionModule class. +// The listener may also specify, when the instance is created, whether to be +// notified about events for a particular session or all sessions. class WvCdmEventListener { public: WvCdmEventListener() {} + WvCdmEventListener(CdmSessionId& session_id) : session_id_(session_id) {} virtual ~WvCdmEventListener() {} virtual void onEvent(const CdmSessionId& session_id, CdmEventType cdm_event) = 0; + virtual CdmSessionId session_id() { return session_id_; } + private: + CdmSessionId session_id_; + CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener); }; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 44815b6b..e2ec0da5 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -16,6 +16,7 @@ typedef std::string CdmKeyMessage; typedef std::string CdmKeyResponse; typedef std::string KeyId; typedef std::string CdmSessionId; +typedef std::string CdmKeySetId; typedef std::string RequestId; typedef uint32_t CryptoResult; typedef uint32_t CryptoSessionId; @@ -50,7 +51,8 @@ enum CdmEventType { enum CdmLicenseType { kLicenseTypeOffline, - kLicenseTypeStreaming + kLicenseTypeStreaming, + kLicenseTypeRelease }; // forward class references diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 6a87ea41..e2f3af24 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -7,6 +7,7 @@ #include "buffer_reader.h" #include "cdm_session.h" +#include "clock.h" #include "crypto_engine.h" #include "device_files.h" #include "license_protocol.pb.h" @@ -35,10 +36,13 @@ using video_widevine_server::sdk::ProvisioningRequest; using video_widevine_server::sdk::ProvisioningResponse; using video_widevine_server::sdk::SignedProvisioningMessage; -typedef std::map::const_iterator CdmSessionIter; +typedef std::map::const_iterator CdmSessionIter; +typedef std::map::iterator CdmReleaseKeySetIter; CdmEngine::CdmEngine() : provisioning_session_(NULL) { Properties::Init(); + Clock clock; + srand(static_cast(clock.GetCurrentTime() & 0xFFFFFFFF)); } CdmEngine::~CdmEngine() { @@ -65,23 +69,22 @@ CdmResponseType CdmEngine::OpenSession( return KEY_ERROR; } - // TODO(edwinwong, rfrias): Save key_system in session for validation checks CdmSession* new_session = new CdmSession(); if (!new_session) { + LOGE("CdmEngine::OpenSession: session creation failed"); return KEY_ERROR; } - if (new_session->session_id().empty()) { + CdmSessionId new_session_id = new_session->session_id(); + + if (new_session_id.empty()) { LOGE("CdmEngine::OpenSession: failure to generate session ID"); delete(new_session); return UNKNOWN_ERROR; } - CdmSessionId new_session_id = new_session->session_id(); - CdmResponseType sts = new_session->Init(); if (sts != NO_ERROR) { - LOGE("CdmEngine::OpenSession: bad session init"); delete(new_session); return sts; } @@ -91,6 +94,24 @@ CdmResponseType CdmEngine::OpenSession( return NO_ERROR; } +CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::OpenKeySetSession"); + + if (key_set_id.empty()) { + LOGI("CdmEngine::OpenKeySetSession: invalid key set id"); + return KEY_ERROR; + } + + CdmSessionId session_id; + CdmResponseType sts = OpenSession(KEY_SYSTEM, &session_id); + + if (sts != NO_ERROR) + return sts; + + release_key_sets_[key_set_id] = session_id; + return NO_ERROR; +} + CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { LOGI("CdmEngine::CloseSession"); @@ -108,10 +129,24 @@ CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { return NO_ERROR; } +CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::CloseKeySetSession"); + + CdmReleaseKeySetIter iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s", + key_set_id.c_str()); + return KEY_ERROR; + } + + CdmResponseType sts = CloseSession(iter->second); + release_key_sets_.erase(iter); + return sts; +} + CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, - bool is_key_system_present, - const CdmKeySystem& key_system, + const CdmKeySetId& key_set_id, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, @@ -119,14 +154,36 @@ CdmResponseType CdmEngine::GenerateKeyRequest( std::string* server_url) { LOGI("CdmEngine::GenerateKeyRequest"); - CdmSessionIter iter = sessions_.find(session_id); - if (iter == sessions_.end()) { - LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", session_id.c_str()); - return KEY_ERROR; + CdmSessionId id = session_id; + CdmResponseType sts; + + if (license_type == kLicenseTypeRelease) { + if (key_set_id.empty()) { + LOGE("CdmEngine::GenerateKeyRequest: invalid key set ID"); + return UNKNOWN_ERROR; + } + + if (!session_id.empty()) { + LOGE("CdmEngine::GenerateKeyRequest: invalid session ID = %s", + session_id.c_str()); + return UNKNOWN_ERROR; + } + + CdmReleaseKeySetIter iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::GenerateKeyRequest: key set ID not found = %s", + key_set_id.c_str()); + return UNKNOWN_ERROR; + } + + id = iter->second; } - if (is_key_system_present) { - // TODO(edwinwong, rfrias): validate key_system has not changed + CdmSessionIter iter = sessions_.find(id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", + id.c_str()); + return KEY_ERROR; } if (!key_request) { @@ -136,33 +193,60 @@ CdmResponseType CdmEngine::GenerateKeyRequest( key_request->clear(); - // TODO(edwinwong, rfrias): need to pass in license type and app parameters - CdmResponseType sts = iter->second->GenerateKeyRequest(init_data, - license_type, - app_parameters, - key_request, - server_url); + if (license_type == kLicenseTypeRelease) { + sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease); + if (sts != KEY_ADDED) { + LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed," + "sts = %d", (int)sts); + return sts; + } + } + + sts = iter->second->GenerateKeyRequest(init_data, license_type, + app_parameters, key_request, + server_url); if (KEY_MESSAGE != sts) { - LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d", - (int)sts); + LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, " + "sts = %d", (int)sts); return sts; } - // TODO(edwinwong, rfrias): persist init_data, license_type, app_parameters - // in session + if (license_type == kLicenseTypeRelease) { + OnKeyReleaseEvent(key_set_id); + } return KEY_MESSAGE; } CdmResponseType CdmEngine::AddKey( const CdmSessionId& session_id, - const CdmKeyResponse& key_data) { + const CdmKeyResponse& key_data, + CdmKeySetId& key_set_id) { LOGI("CdmEngine::AddKey"); - CdmSessionIter iter = sessions_.find(session_id); + CdmSessionId id = session_id; + bool license_type_release = session_id.empty(); + + if (license_type_release) { + if (key_set_id.empty()) { + LOGI("CdmEngine::AddKey: invalid key set id"); + return KEY_ERROR; + } + + CdmReleaseKeySetIter iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::AddKey: key set id not found = %s", key_set_id.c_str()); + return KEY_ERROR; + } + + id = iter->second; + } + + CdmSessionIter iter = sessions_.find(id); + if (iter == sessions_.end()) { - LOGE("CdmEngine::AddKey: session_id not found = %s", session_id.c_str()); + LOGE("CdmEngine::AddKey: session id not found = %s", id.c_str()); return KEY_ERROR; } @@ -171,18 +255,41 @@ CdmResponseType CdmEngine::AddKey( return KEY_ERROR; } - CdmResponseType sts = iter->second->AddKey(key_data); + CdmResponseType sts = iter->second->AddKey(key_data, &key_set_id); + if (KEY_ADDED != sts) { LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts); + return sts; } - EnablePolicyTimer(); - return sts; + + if (!license_type_release) { + EnablePolicyTimer(); + } + + return KEY_ADDED; } -CdmResponseType CdmEngine::CancelKeyRequest( +CdmResponseType CdmEngine::RestoreKey( const CdmSessionId& session_id, - bool is_key_system_present, - const CdmKeySystem& key_system) { + const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::RestoreKey"); + + if (key_set_id.empty()) { + LOGI("CdmEngine::RestoreKey: invalid key set id"); + return KEY_ERROR; + } + + CdmSessionIter iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::RestoreKey: session_id not found = %s ", + session_id.c_str()); + return UNKNOWN_ERROR; + } + + return iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeOffline); +} + +CdmResponseType CdmEngine::CancelKeyRequest(const CdmSessionId& session_id) { LOGI("CdmEngine::CancelKeyRequest"); //TODO(gmorgan): Issue: what is semantics of canceling a key request. Should @@ -197,10 +304,6 @@ CdmResponseType CdmEngine::CancelKeyRequest( return KEY_ERROR; } - if (is_key_system_present) { - // TODO(edwinwong, rfrias): validate key_system has not changed - } - // TODO(edwinwong, rfrias): unload keys here DisablePolicyTimer(); return NO_ERROR; @@ -747,37 +850,64 @@ bool CdmEngine::ExtractWidevinePssh( while (1) { // size of PSSH atom, used for skipping uint32_t size; - if (!reader.Read4(&size)) return false; + if (!reader.Read4(&size)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH atom size"); + return false; + } // "pssh" std::vector pssh; - if (!reader.ReadVec(&pssh, 4)) return false; - if (memcmp(&pssh[0], "pssh", 4)) return false; + if (!reader.ReadVec(&pssh, 4)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal"); + return false; + } + if (memcmp(&pssh[0], "pssh", 4)) { + LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present"); + return false; + } // flags uint32_t flags; - if (!reader.Read4(&flags)) return false; - if (flags != 0) return false; + if (!reader.Read4(&flags)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags"); + return false; + } + if (flags != 0) { + LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero"); + return false; + } // system id std::vector system_id; - if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) return false; + if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID"); + return false; + } if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) { // skip the remaining contents of the atom, // after size field, atom name, flags and system id if (!reader.SkipBytes( - size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) return false; + size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom"); + return false; + } continue; } // size of PSSH box uint32_t pssh_length; - if (!reader.Read4(&pssh_length)) return false; + if (!reader.Read4(&pssh_length)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH box size"); + return false; + } output->clear(); - if (!reader.ReadString(output, pssh_length)) return false; + if (!reader.ReadString(output, pssh_length)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH"); + return false; + } return true; } @@ -806,4 +936,12 @@ void CdmEngine::OnTimerEvent() { } } +void CdmEngine::OnKeyReleaseEvent(CdmKeySetId key_set_id) { + + for (CdmSessionIter iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + iter->second->OnKeyReleaseEvent(key_set_id); + } +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 75503f1b..23368aa5 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -5,12 +5,14 @@ #include #include +#include #include "clock.h" #include "cdm_engine.h" #include "crypto_engine.h" #include "device_files.h" #include "log.h" +#include "openssl/sha.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -28,17 +30,22 @@ CdmResponseType CdmSession::Init() { crypto_session_ = crypto_engine->CreateSession(session_id_); if (!crypto_session_) { + LOGE("CdmSession::Init crypto session creation failure"); return UNKNOWN_ERROR; } std::string token; if (Properties::use_certificates_as_identification()) { - if (!LoadDeviceCertificate(&token, &wrapped_key_)) + if (!LoadDeviceCertificate(&token, &wrapped_key_)) { + LOGE("CdmSession::Init provisioning needed"); return NEED_PROVISIONING; + } } else { - if (!crypto_engine->GetToken(&token)) + if (!crypto_engine->GetToken(&token)) { + LOGE("CdmSession::Init token retrieval failure"); return UNKNOWN_ERROR; + } } if (license_parser_.Init(token, crypto_session_, &policy_engine_)) @@ -60,6 +67,49 @@ bool CdmSession::DestroySession() { return true; } +CdmResponseType CdmSession::RestoreOfflineSession( + const CdmKeySetId& key_set_id, + const CdmLicenseType license_type) { + key_set_id_ = key_set_id; + + // Retrieve license information from persistent store + DeviceFiles::LicenseState license_state; + + if (!DeviceFiles::RetrieveLicense(key_set_id, &license_state, + &offline_pssh_data_, + &offline_key_request_, + &offline_key_response_, + &offline_key_renewal_request_, + &offline_key_renewal_response_, + &offline_release_server_url_)) { + LOGE("CdmSession::Init failed to retrieve license. key set id = %s", + key_set_id.c_str()); + return UNKNOWN_ERROR; + } + + if (license_state != DeviceFiles::kLicenseStateActive) { + LOGE("CdmSession::Init invalid offline license state = %s", license_state); + return UNKNOWN_ERROR; + } + + if (Properties::use_certificates_as_identification()) { + if (!crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) { + return NEED_PROVISIONING; + } + } + + if (license_type == kLicenseTypeOffline) { + if (!license_parser_.RestoreOfflineLicense(offline_key_request_, + offline_key_response_, + offline_key_renewal_response_)) + return UNKNOWN_ERROR; + } + + license_received_ = true; + license_type_ = license_type; + return KEY_ADDED; +} + bool CdmSession::VerifySession(const CdmKeySystem& key_system, const CdmInitData& init_data) { // TODO(gmorgan): Compare key_system and init_data with value received @@ -70,7 +120,7 @@ bool CdmSession::VerifySession(const CdmKeySystem& key_system, CdmResponseType CdmSession::GenerateKeyRequest( const CdmInitData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, + const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, std::string* server_url) { @@ -82,7 +132,6 @@ CdmResponseType CdmSession::GenerateKeyRequest( reinitialize_session_ = false; } - if (!crypto_session_) { LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); return UNKNOWN_ERROR; @@ -93,10 +142,14 @@ CdmResponseType CdmSession::GenerateKeyRequest( return UNKNOWN_ERROR; } - if (license_received_) { + license_type_ = license_type; + + if (license_type_ == kLicenseTypeRelease) { + return GenerateReleaseRequest(key_request, server_url); + } + else if (license_received_) { // renewal return Properties::require_explicit_renew_request() ? - UNKNOWN_ERROR : GenerateRenewalRequest(key_request, - server_url); + UNKNOWN_ERROR : GenerateRenewalRequest(key_request, server_url); } else { CdmInitData pssh_data; @@ -117,14 +170,22 @@ CdmResponseType CdmSession::GenerateKeyRequest( key_request, server_url)) { return KEY_ERROR; - } else { - return KEY_MESSAGE; } + + if (license_type_ == kLicenseTypeOffline) { + offline_pssh_data_ = pssh_data; + offline_key_request_ = *key_request; + offline_release_server_url_ = *server_url; + } + + return KEY_MESSAGE; } } // AddKey() - Accept license response and extract key info. -CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { +CdmResponseType CdmSession::AddKey( + const CdmKeyResponse& key_response, + CdmKeySetId* key_set_id) { if (!crypto_session_) { LOGW("CdmSession::AddKey: Invalid crypto session"); return UNKNOWN_ERROR; @@ -135,17 +196,34 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) { return UNKNOWN_ERROR; } - if (license_received_) { + if (license_type_ == kLicenseTypeRelease) { + return ReleaseKey(key_response); + } + else if (license_received_) { // renewal return Properties::require_explicit_renew_request() ? UNKNOWN_ERROR : RenewKey(key_response); } else { CdmResponseType sts = license_parser_.HandleKeyResponse(key_response); - if (sts == KEY_ADDED) - license_received_ = true; + if (sts != KEY_ADDED) + return sts; - return sts; + license_received_ = true; + + if (license_type_ == kLicenseTypeOffline) { + offline_key_response_ = key_response; + key_set_id_ = GenerateKeySetId(offline_pssh_data_); + if (!StoreLicense(true)) { + LOGE("CdmSession::AddKey: Unable to store license"); + ReInit(); + key_set_id_.clear(); + return UNKNOWN_ERROR; + } + } + + *key_set_id = key_set_id_; + return KEY_ADDED; } } @@ -207,17 +285,49 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted, // session keys. CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, std::string* server_url) { - if (!license_parser_.PrepareKeyRenewalRequest(key_request, - server_url)) { + if (!license_parser_.PrepareKeyUpdateRequest(true, key_request, server_url)) return KEY_ERROR; - } else { - return KEY_MESSAGE; + + if (license_type_ == kLicenseTypeOffline) { + offline_key_renewal_request_ = *key_request; } + return KEY_MESSAGE; } // RenewKey() - Accept renewal response and update key info. CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { - return license_parser_.HandleKeyRenewalResponse(key_response); + CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(true, + key_response); + if (sts != KEY_ADDED) + return sts; + + if (license_type_ == kLicenseTypeOffline) { + offline_key_renewal_response_ = key_response; + if (!StoreLicense(true)) + return UNKNOWN_ERROR; + } + return KEY_ADDED; +} + +CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, + std::string* server_url) { + if (license_parser_.PrepareKeyUpdateRequest(false, key_request, + server_url)) { + // Mark license as being released + if (!StoreLicense(false)) + return UNKNOWN_ERROR; + + return KEY_MESSAGE; + } + return UNKNOWN_ERROR; +} + +// ReleaseKey() - Accept release response and release license. +CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { + CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false, + key_response); + DeviceFiles::DeleteLicense(key_set_id_); + return sts; } bool CdmSession::IsKeyValid(const KeyId& key_id) { @@ -227,11 +337,39 @@ bool CdmSession::IsKeyValid(const KeyId& key_id) { } CdmSessionId CdmSession::GenerateSessionId() { - static const std::string kSessionPrefix("Session"); static int session_num = 1; // TODO(rkuroiwa): Want this to be unique. Probably doing Hash(time+init_data) // to get something that is reasonably unique. - return kSessionPrefix + IntToString(++session_num); + return SESSION_ID_PREFIX + IntToString(++session_num); +} + + +CdmSessionId CdmSession::GenerateKeySetId(CdmInitData& pssh_data) { + Clock clock; + int64_t current_time = clock.GetCurrentTime(); + std::string key_set_id; + + while (key_set_id.empty()) { + int random = rand(); + + std::vector hash(SHA256_DIGEST_LENGTH, 0); + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, pssh_data.data(), pssh_data.size()); + SHA256_Update(&sha256, ¤t_time, sizeof(int64_t)); + SHA256_Update(&sha256, &random, sizeof(random)); + SHA256_Final(&hash[0], &sha256); + for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + hash[i%(SHA256_DIGEST_LENGTH/4)] ^= hash[i]; + } + hash.resize(SHA256_DIGEST_LENGTH/4); + key_set_id = KEY_SET_ID_PREFIX + b2a_hex(hash); + + if (DeviceFiles::LicenseExists(key_set_id)) { // key set collision + key_set_id.clear(); + } + } + return key_set_id; } bool CdmSession::LoadDeviceCertificate(std::string* certificate, @@ -240,6 +378,18 @@ bool CdmSession::LoadDeviceCertificate(std::string* certificate, wrapped_key); } +bool CdmSession::StoreLicense(bool active) { + DeviceFiles::LicenseState state = DeviceFiles::kLicenseStateReleasing; + if (active) + state = DeviceFiles::kLicenseStateActive; + + return DeviceFiles::StoreLicense(key_set_id_, state, offline_pssh_data_, + offline_key_request_, offline_key_response_, + offline_key_renewal_request_, + offline_key_renewal_response_, + offline_release_server_url_); +} + bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { std::pair result = listeners_.insert(listener); return result.second; @@ -258,7 +408,22 @@ void CdmSession::OnTimerEvent() { if (event_occurred) { for (CdmEventListenerIter iter = listeners_.begin(); iter != listeners_.end(); ++iter) { - (*iter)->onEvent(session_id(), event); + CdmSessionId id = (*iter)->session_id(); + if (id.empty() || (id.compare(session_id_) == 0)) { + (*iter)->onEvent(session_id_, event); + } + } + } +} + +void CdmSession::OnKeyReleaseEvent(CdmKeySetId key_set_id) { + if (key_set_id_.compare(key_set_id) == 0) { + for (CdmEventListenerIter iter = listeners_.begin(); + iter != listeners_.end(); ++iter) { + CdmSessionId id = (*iter)->session_id(); + if (id.empty() || (id.compare(session_id_) == 0)) { + (*iter)->onEvent(session_id_, LICENSE_EXPIRED_EVENT); + } } } } diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 3e9f8e9c..9d4ca854 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -18,10 +18,14 @@ namespace wvcdm { const char* DeviceFiles::kBasePath = "/data/mediadrm/IDM"; const char* DeviceFiles::kPathDelimiter = "/"; const char* DeviceFiles::kDeviceCertificateFileName = "cert.bin"; +const char* DeviceFiles::kLicenseFileNameExt = ".lic"; // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::License; +using video_widevine_client::sdk::License_LicenseState_ACTIVE; +using video_widevine_client::sdk::License_LicenseState_RELEASING; bool DeviceFiles::StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key) { @@ -107,6 +111,148 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate, return true; } +bool DeviceFiles::StoreLicense( + const std::string& key_set_id, + const LicenseState state, + const CdmInitData& pssh_data, + const CdmKeyMessage& license_request, + const CdmKeyResponse& license_message, + const CdmKeyMessage& license_renewal_request, + const CdmKeyResponse& license_renewal, + const std::string& release_server_url) { + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::LICENSE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + License* license = file.mutable_license(); + switch(state) { + case kLicenseStateActive: + license->set_state(License_LicenseState_ACTIVE); + break; + case kLicenseStateReleasing: + license->set_state(License_LicenseState_RELEASING); + break; + default: + LOGW("DeviceFiles::StoreLicense: Unknown license state: %u", state); + return false; + break; + } + license->set_pssh_data(pssh_data); + license->set_license_request(license_request); + license->set_license(license_message); + license->set_renewal_request(license_renewal_request); + license->set_renewal(license_renewal); + license->set_release_server_url(release_server_url); + + std::string serialized_string; + file.SerializeToString(&serialized_string); + + // calculate SHA hash + std::string hash; + if (!Hash(serialized_string, &hash)) { + LOGW("DeviceFiles::StoreLicense: Hash computation failed"); + return false; + } + + // File in hashed file data + HashedFile hashed_file; + hashed_file.set_file(serialized_string); + hashed_file.set_hash(hash); + + hashed_file.SerializeToString(&serialized_string); + + std::string file_name = key_set_id + kLicenseFileNameExt; + return StoreFile(file_name.c_str(), serialized_string); +} + +bool DeviceFiles::RetrieveLicense( + const std::string& key_set_id, + LicenseState* state, + CdmInitData* pssh_data, + CdmKeyMessage* license_request, + CdmKeyResponse* license_message, + CdmKeyMessage* license_renewal_request, + CdmKeyResponse* license_renewal, + std::string* release_server_url) { + std::string serialized_hashed_file; + std::string file_name = key_set_id + kLicenseFileNameExt; + if (!RetrieveFile(file_name.c_str(), &serialized_hashed_file)) + return false; + + HashedFile hashed_file; + if (!hashed_file.ParseFromString(serialized_hashed_file)) { + LOGW("DeviceFiles::RetrieveLicense: Unable to parse hash file"); + return false; + } + + std::string hash; + if (!Hash(hashed_file.file(), &hash)) { + LOGW("DeviceFiles::RetrieveLicense: Hash computation failed"); + return false; + } + + if (hash.compare(hashed_file.hash())) { + LOGW("DeviceFiles::RetrieveLicense: Hash mismatch"); + return false; + } + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(hashed_file.file())) { + LOGW("DeviceFiles::RetrieveLicense: Unable to parse file"); + return false; + } + + if (file.type() != video_widevine_client::sdk::File::LICENSE) { + LOGW("DeviceFiles::RetrieveLicense: Incorrect file type"); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveLicense: Incorrect file version"); + return false; + } + + if (!file.has_license()) { + LOGW("DeviceFiles::RetrieveLicense: License not present"); + return false; + } + + License license = file.license(); + + switch(license.state()) { + case License_LicenseState_ACTIVE: + *state = kLicenseStateActive; + break; + case License_LicenseState_RELEASING: + *state = kLicenseStateReleasing; + break; + default: + LOGW("DeviceFiles::RetrieveLicense: Unrecognized license state: %u", + kLicenseStateUnknown); + *state = kLicenseStateUnknown; + break; + } + *pssh_data = license.pssh_data(); + *license_request = license.license_request(); + *license_message = license.license(); + *license_renewal_request = license.renewal_request(); + *license_renewal = license.renewal(); + *release_server_url = license.release_server_url(); + return true; +} + +bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { + std::string path = GetBasePath(kBasePath) + key_set_id + kLicenseFileNameExt; + return File::Remove(path); +} + +bool DeviceFiles::LicenseExists(const std::string& key_set_id) { + std::string path = GetBasePath(kBasePath) + key_set_id + kLicenseFileNameExt; + return File::Exists(path); +} + bool DeviceFiles::Hash(const std::string& data, std::string* hash) { if (!hash) return false; @@ -147,6 +293,7 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) { return false; } + LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size()); return true; } @@ -184,6 +331,8 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { return false; } + LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(), + data->size()); return true; } diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index 339ed9c1..d561c831 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -19,10 +19,18 @@ message DeviceCertificate { } message License { - optional bytes key_set_id = 1; + enum LicenseState { + ACTIVE = 1; + RELEASING = 2; + } + + optional LicenseState state = 1; optional bytes pssh_data = 2; optional bytes license_request = 3; optional bytes license = 4; + optional bytes renewal_request = 5; + optional bytes renewal = 6; + optional bytes release_server_url = 7; } message File { @@ -38,7 +46,7 @@ message File { optional FileType type = 1; optional FileVersion version = 2 [default = VERSION_1]; optional DeviceCertificate device_certificate = 3; - repeated License licenses = 4; + optional License license = 4; } message HashedFile { diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 753f9b6b..cf4f8520 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -36,8 +36,6 @@ using video_widevine_server::sdk::License; using video_widevine_server::sdk::License_KeyContainer; using video_widevine_server::sdk::LicenseError; using video_widevine_server::sdk::SignedMessage; -using video_widevine_server::sdk::STREAMING; -using video_widevine_server::sdk::VERSION_2_1; static std::vector ExtractContentKeys(const License& license) { @@ -97,7 +95,7 @@ bool CdmLicense::Init(const std::string& token, bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, const CdmLicenseType license_type, - CdmAppParameterMap& app_parameters, + const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, std::string* server_url) { if (!session_ || @@ -180,7 +178,20 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, LicenseRequest_ContentIdentification_CENC* cenc_content_id = content_id->mutable_cenc_id(); cenc_content_id->add_pssh(init_data); - cenc_content_id->set_license_type(STREAMING); + + switch (license_type) { + case kLicenseTypeOffline: + cenc_content_id->set_license_type(video_widevine_server::sdk::OFFLINE); + break; + case kLicenseTypeStreaming: + cenc_content_id->set_license_type(video_widevine_server::sdk::STREAMING); + break; + default: + LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %u", + (int)license_type); + return false; + break; + } cenc_content_id->set_request_id(request_id); // TODO(jfore): The time field will be updated once the cdm wrapper @@ -197,7 +208,7 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, } license_request.set_key_control_nonce(UintToString(nonce)); LOGD("PrepareKeyRequest: nonce=%u", nonce); - license_request.set_protocol_version(VERSION_2_1); + license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1); // License request is complete. Serialize it. std::string serialized_license_req; @@ -215,6 +226,7 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, } if (license_request_signature.empty()) { + LOGE("CdmLicense::PrepareKeyRequest: License request signature empty"); signed_request->clear(); return false; } @@ -231,22 +243,27 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, return true; } -bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, - std::string* server_url) { +bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url) { if (!session_) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: Invalid crypto session"); return false; } if (!signed_request) { - LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided."); + LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided"); return false; } if (!server_url) { - LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided."); + LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided"); return false; } LicenseRequest license_request; - license_request.set_type(LicenseRequest::RENEWAL); + if (is_renewal) + license_request.set_type(LicenseRequest::RENEWAL); + else + license_request.set_type(LicenseRequest::RELEASE); LicenseRequest_ContentIdentification_ExistingLicense* current_license = license_request.mutable_content_id()->mutable_license(); @@ -259,8 +276,8 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, return false; } license_request.set_key_control_nonce(UintToString(nonce)); - LOGD("PrepareKeyRenewalRequest: nonce=%u", nonce); - license_request.set_protocol_version(VERSION_2_1); + LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce); + license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1); // License request is complete. Serialize it. std::string serialized_license_req; @@ -272,7 +289,11 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, &license_request_signature)) return false; - if (license_request_signature.empty()) return false; + if (license_request_signature.empty()) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: empty license request" + " signature"); + return false; + } // Put serialize license request and signature together SignedMessage signed_message; @@ -369,44 +390,60 @@ CdmResponseType CdmLicense::HandleKeyResponse( } } -CdmResponseType CdmLicense::HandleKeyRenewalResponse( +CdmResponseType CdmLicense::HandleKeyUpdateResponse( + bool is_renewal, const CdmKeyResponse& license_response) { if (!session_) { return KEY_ERROR; } if (license_response.empty()) { - LOGE("CdmLicense::HandleKeyRenewalResponse : Empty license response."); + LOGE("CdmLicense::HandleKeyUpdateResponse : Empty license response."); return KEY_ERROR; } SignedMessage signed_response; - if (!signed_response.ParseFromString(license_response)) + if (!signed_response.ParseFromString(license_response)) { + LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message"); return KEY_ERROR; + } if (signed_response.type() == SignedMessage::ERROR) { return HandleKeyErrorResponse(signed_response); } - if (!signed_response.has_signature()) + if (!signed_response.has_signature()) { + LOGE("CdmLicense::HandleKeyUpdateResponse: signature missing"); return KEY_ERROR; + } License license; - if (!license.ParseFromString(signed_response.msg())) + if (!license.ParseFromString(signed_response.msg())) { + LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse license" + " from signed message"); return KEY_ERROR; + } - if (!license.has_id()) return KEY_ERROR; + if (!license.has_id()) { + LOGE("CdmLicense::HandleKeyUpdateResponse: license id not present"); + return KEY_ERROR; + } if (license.id().version() > license_id_.version()) { // This is the normal case. license_id_.CopyFrom(license.id()); - if (license.policy().has_renewal_server_url() && - license.policy().renewal_server_url().size() > 0) { - server_url_ = license.policy().renewal_server_url(); + if (is_renewal) { + if (license.policy().has_renewal_server_url() && + license.policy().renewal_server_url().size() > 0) { + server_url_ = license.policy().renewal_server_url(); + } } policy_engine_->UpdateLicense(license); + if (!is_renewal) + return KEY_ADDED; + std::vector key_array = ExtractContentKeys(license); if (session_->RefreshKeys(signed_response.msg(), @@ -423,15 +460,67 @@ CdmResponseType CdmLicense::HandleKeyRenewalResponse( // This isn't supposed to happen. // TODO(jfore): Handle wrap? We can miss responses and that should be // considered normal until retries are exhausted. + LOGE("CdmLicense::HandleKeyUpdateResponse: license version: expected > %u," + " actual = %u", license_id_.version(), license.id().version()); return KEY_ERROR; } +bool CdmLicense::RestoreOfflineLicense( + CdmKeyMessage& license_request, + CdmKeyResponse& license_response, + CdmKeyResponse& license_renewal_response) { + + if (license_request.empty() || license_response.empty()) { + LOGE("CdmLicense::RestoreOfflineLicense: key_request or response empty: " + "%u %u", license_request.size(), license_response.size()); + return false; + } + + SignedMessage signed_request; + if (!signed_request.ParseFromString(license_request)) { + LOGE("CdmLicense::RestoreOfflineLicense: license_request parse failed"); + return false; + } + + if (signed_request.type() != SignedMessage::LICENSE_REQUEST) { + LOGE("CdmLicense::RestoreOfflineLicense: license request type: expected = " + "%d, actual = %d", + SignedMessage::LICENSE_REQUEST, + signed_request.type()); + return false; + } + + if (Properties::use_certificates_as_identification()) { + key_request_ = signed_request.msg(); + } + else { + if (!session_->GenerateDerivedKeys(signed_request.msg())) + return false; + } + + CdmResponseType sts = HandleKeyResponse(license_response); + + if (sts != KEY_ADDED) + return false; + + if (!license_renewal_response.empty()) { + sts = HandleKeyUpdateResponse(true, license_renewal_response); + + if (sts != KEY_ADDED) + return false; + } + + return true; +} + CdmResponseType CdmLicense::HandleKeyErrorResponse( const SignedMessage& signed_message) { LicenseError license_error; - if (!license_error.ParseFromString(signed_message.msg())) + if (!license_error.ParseFromString(signed_message.msg())) { + LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error"); return KEY_ERROR; + } switch (license_error.error_code()) { case LicenseError::INVALID_CREDENTIALS: @@ -440,6 +529,8 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( return DEVICE_REVOKED; case LicenseError::SERVICE_UNAVAILABLE: default: + LOGW("CdmLicense::HandleKeyErrorResponse: Unknwon error type = %d", + license_error.error_code()); return KEY_ERROR; } } diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 6c10eb21..bed53f37 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -36,14 +36,14 @@ class WvCdmEngineTest : public testing::Test { const std::string& init_data) { wvcdm::CdmAppParameterMap app_parameters; std::string server_url; + std::string key_set_id; EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_, - true, // is_key_system_present - key_system, - init_data, - kLicenseTypeStreaming, - app_parameters, - &key_msg_, - &server_url), wvcdm::KEY_MESSAGE); + key_set_id, + init_data, + kLicenseTypeStreaming, + app_parameters, + &key_msg_, + &server_url), wvcdm::KEY_MESSAGE); } void GenerateRenewalRequest(const std::string& key_system, @@ -125,7 +125,7 @@ class WvCdmEngineTest : public testing::Test { return ""; } - url_request.PostRequest(key_msg_); + url_request.PostRequestChunk(key_msg_); std::string http_response; std::string message_body; int resp_bytes = url_request.GetResponse(http_response); @@ -166,7 +166,9 @@ class WvCdmEngineTest : public testing::Test { EXPECT_EQ(cdm_engine_.RenewKey(session_id_, resp), wvcdm::KEY_ADDED); } else { - EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp), wvcdm::KEY_ADDED); + std::string key_set_id; + EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp, key_set_id), + wvcdm::KEY_ADDED); } } diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 8b2ed697..7088a629 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -6,6 +6,136 @@ namespace wvcdm { +namespace { + std::string kKeySetId1 = "ksid05e79dab2750370195d1"; + std::string kKeySetId2 = "ksid17fe1e9005934171bbd1"; + std::string kKeySetId3 = "ksid2eadb793100566012934"; + + std::string kPsshData1 = "0801121093789920E8D6520098577DF8F2DD5546"; + std::string kPsshData2 = "0801121030313233343536373839616263646566"; + std::string kPsshData3 = "08011210e02562e04cd55351b14b3d748d36ed8e"; + + std::string kKeyRequest1 = "0801128D0C0ACF0B080112EF090AB0020802121007D9FFDE" + "13AA95C122678053362136BD18D6DDC78C05228E023082010A0282010100B2873F12C61" + "8883986B67A404733168D34676EA3261231B4EB12CB6F862AAE529D21B449D78E7A8FBB" + "9CC1A8AE119D89EC571EA23EE92517AFB86EA089E81CF3A773AEDEDFC1979AEE115D657" + "FE372CBF4178E57F60BEDD15491EF28891ED8663437019C0EF5F79FD0ADDAAE87281DD9" + "10ED10356DD6B606C0DD2452343329151BD23A42060869252892BD2C0368787E880F716" + "438E5835BC93FED65D0017D8758E20E5E614B44BAFAF7286484624CC5EB109D85F1C048" + "9B25A75528C0CD23961067192EF65766EAB5E97FE7EBE25D63DDE41BB34048B8A378EAE" + "AEBC86C44AB4AA757F29B48C9476660DCC4EE656EB35AC309213399CBEF38394FE32B88" + "3F9B02030100012899203001128002435CAFA39AD7DC12A40F943FE8DE10C5DD911059D" + "7B0990742E11419B2E6E797EAE46B5D017F0EF3F429AA43E4F570BBE46F56BBCEACBDFA" + "8495E4451249D39D5FEFC4F5557498B233C4C9C101E2388E107582D3C3FBF266AB9FE96" + "A40405A2098583B33D7AD083E105458DF7AE92FD517D538B4671614DA0D167F9661EE0C" + "574AB95A5BE87B1BEE27659D875A37A1730F84033ACB6BCA8D3C575A2692E7390F062BC" + "E1E58D9C621A1A11CB215615ED0C5C12"; + std::string kKeyRequest2 = "0801128D0C0ACF0B080112EF090AB0020802121007D9FFDE" + "13AA95C122678053362136BD188BDCC78C05228E023082010A0282010100C53F667AAA8" + "27758D3ABF81DAFA223373473EE13E4C500A09CD35CE2620D5DA0D987A237EBFCE1F793" + "D6F88187749A85B3AF4D737899EF19C3029461E95202E9DF91DD5869FE482FCA17160AF" + "7AC380D03880EBE3EA55B7FB3ED5ED6B9FAD19234AA75BB642A9A86AEF7CDA86F7B2EC7" + "C40C31C380F95C1F3C1564B012D530A7971AA656BBD27DAB715F0D5771FB7608453AF88" + "A331E3E5852F6F9B076A95C807307E541C39216D64B02D180A1009AFB60777EF7C72275" + "033ABD6FDF12145FFD379B6CB25F1127A783A40D65BF95AB74A08E39209EF2C0341A92A" + "217FD223734C7D1F0DC0D5B50FBA521FF1F841D1E5ED18DFC13A6D28E5A0A4FACBE6C78" + "31B5020301000128992030011280022DBC4444671C0C4CF0FBB3BB30C80C78FAFA67223" + "C6FE3F0BB495B1959B7472E8A64BC13838B3FB731FAC0609FEAB325FE8B7358C2F63D5E" + "E6C0380B85DBA3FE207B9D6AE7C31467A36A9E8D3D786CC4C383BC2774131D6C099D06D" + "052ED13A4545A0026A4FC949DE3B79D1C0C6A0D581A3D3598E46D3E4AC827C94045D693" + "37CAF103CC8839EA7FF020C0721AB17B140640C5E31C1727073CA48F445DA5EE55521D8" + "85732BAA17BD672B62C717ADBC13F235"; + std::string kKeyRequest3 = "0801128D0C0ACF0B080112EF090AB0020802121007D9FFDE" + "13AA95C122678053362136BD18D6DDC78C05228E023082010A0282010100B2873F12C61" + "8883986B67A404733168D34676EA3261231B4EB12CB6F862AAE529D21B449D78E7A8FBB" + "9CC1A8AE119D89EC571EA23EE92517AFB86EA089E81CF3A773AEDEDFC1979AEE115D657" + "FE372CBF4178E57F60BEDD15491EF28891ED8663437019C0EF5F79FD0ADDAAE87281DD9" + "10ED10356DD6B606C0DD2452343329151BD23A42060869252892BD2C0368787E880F716" + "438E5835BC93FED65D0017D8758E20E5E614B44BAFAF7286484624CC5EB109D85F1C048" + "9B25A75528C0CD23961067192EF65766EAB5E97FE7EBE25D63DDE41BB34048B8A378EAE" + "AEBC86C44AB4AA757F29B48C9476660DCC4EE656EB35AC309213399CBEF38394FE32B88" + "3F9B02030100012899203001128002435CAFA39AD7DC12A40F943FE8DE10C5DD911059D" + "7B0990742E11419B2E6E797EAE46B5D017F0EF3F429AA43E4F570BBE46F56BBCEACBDFA" + "8495E4451249D39D5FEFC4F5557498B233C4C9C101E2388E107582D3C3FBF266AB9FE96" + "A40405A2098583B33D7AD083E105458DF7AE92FD517D538B4671614DA0D167F9661EE0C" + "574AB95A5BE87B1BEE27659D875A37A1730F84033ACB6BCA8D3C575A2692E7390F062BC" + "E1E58D9C621A1A11CB215615ED0C5C12"; + + std::string kKeyResponse1 = "080212C2040A460A0939383736353433323112086D03DC5" + "FD3BB58031A2B596E4B58753250316A5F53785073494E6B377459354E7967396755464B" + "6963455954335A34626738366C77200128001283010801180120809A9E0128809A9E013" + "0AC02426C68747470733A2F2F6A6D7431372E676F6F676C652E636F6D2F766964656F2D" + "6465762F6865617274626561742F63656E633F736F757263653D594F555455424526766" + "964656F5F69643D45474843364F484E624F6F266F617574683D79612E67747371617769" + "646576696E65483C503C1A661210EBA4B8B97E17DC6C45D359222B9327121A50583647C" + "BC8240C2760A79DC72DF7FC0216C0943F12307B9AAED429277A6D0024F93CCA17B7F2BA" + "C19875E041F12E6729A98E0DFF2EA9806D6C20AF801A5A3E19DAF918FC9AF230C8765DF" + "B5BE869DF7320011A80010A10E02562E04CD55351B14B3D748D36ED8E12107C9AC11A9D" + "39304821C47969729A43321A2077E79664AF73B13FC6C55B5B9B2000E174E94F7D9E9BB" + "B58A547A82FAAAF46182002280242340A202031C7ADB3C2F80975499E285CEE0AF8343A" + "F3DF50A49EEBCCEB3747C955FEFB12107E021073F71CF814A9CCAA7008B38CD81A80010" + "A100065901A64A25899A5193664ABF9AF621210874AB758C27A29677D62D2EE092905E2" + "1A207C4AD3CB7A80740215598D5E74883"; + std::string kKeyResponse2 = "080212C2040A460A0939383736353433323112080C1913B" + "45E8893481A2B596E4B58753250316A5F53785073494E6B377459354E7967396755464B" + "6963455954335A34626738366C77200128001283010801180120809A9E0128809A9E013" + "0AC02426C68747470733A2F2F6A6D7431372E676F6F676C652E636F6D2F766964656F2D" + "6465762F6865617274626561742F63656E633F736F757263653D594F555455424526766" + "964656F5F69643D45474843364F484E624F6F266F617574683D79612E67747371617769" + "646576696E65483C503C1A661210A2B662DC43891A32E65E0CC31CD597B21A50046489D" + "7EE8A674A4976800FD0553075498F90FD1997A63AECFF3481BF5CA908502676FA7E7E6B" + "02179FE6C42FAE7351EF85C8494BBE0782CB4C7A5AA6E439590C2D39DD5DB2B1A3DA91E" + "3D43FA5ABDD20011A80010A10E02562E04CD55351B14B3D748D36ED8E1210B0EF7CC3B2" + "0E10F4F704DBCF3142AB611A20F684136083E458C6B2977DB4D1FC559F2AFF2C454FEDF" + "CD68F29D50D351D58DC2002280242340A20223C0E4281615524569AF90EC3BE98349B27" + "0016C5DCC4CFDF11CB55C9DCFB231210888EF5E2311B3B3407549903D8F30D771A80010" + "A100065901A64A25899A5193664ABF9AF621210F38F473DC71703C52CC970FCACFA2111" + "1A20067E176521B6176A97157EDB9D06C"; + std::string kKeyResponse3 = "080212C2040A460A0939383736353433323112086D03DC5" + "FD3BB58031A2B596E4B58753250316A5F53785073494E6B377459354E7967396755464B" + "6963455954335A34626738366C77200128001283010801180120809A9E0128809A9E013" + "0AC02426C68747470733A2F2F6A6D7431372E676F6F676C652E636F6D2F766964656F2D" + "6465762F6865617274626561742F63656E633F736F757263653D594F555455424526766" + "964656F5F69643D45474843364F484E624F6F266F617574683D79612E67747371617769" + "646576696E65483C503C1A661210EBA4B8B97E17DC6C45D359222B9327121A50583647C" + "BC8240C2760A79DC72DF7FC0216C0943F12307B9AAED429277A6D0024F93CCA17B7F2BA" + "C19875E041F12E6729A98E0DFF2EA9806D6C20AF801A5A3E19DAF918FC9AF230C8765DF" + "B5BE869DF7320011A80010A10E02562E04CD55351B14B3D748D36ED8E12107C9AC11A9D" + "39304821C47969729A43321A2077E79664AF73B13FC6C55B5B9B2000E174E94F7D9E9BB" + "B58A547A82FAAAF46182002280242340A202031C7ADB3C2F80975499E285CEE0AF8343A" + "F3DF50A49EEBCCEB3747C955FEFB12107E021073F71CF814A9CCAA7008B38CD81A80010" + "A100065901A64A25899A5193664ABF9AF621210874AB758C27A29677D62D2EE092905E2" + "1A207C4AD3CB7A80740215598D5E74883"; + + std::string kKeyRenewalRequest1 = ""; + std::string kKeyRenewalRequest2 = "0801125E124A1A480A460A0939383736353433323" + "112080C1913B45E8893481A2B596E4B58753250316A5F53785073494E6B377459354E79" + "67396755464B6963455954335A34626738366C772001280018022A0C383231353639373" + "13400000030151A20789770161DD09DD5210E1A399DCA51C7741D2FC83488B731D22F4C" + "B01E74D240"; + std::string kKeyRenewalRequest3 = "0801125E124A1A480A460A0939383736353433323" + "112086D03DC5FD3BB58031A2B596E4B58753250316A5F53785073494E6B377459354E79" + "67396755464B6963455954335A34626738366C772001280018022A0C313736393630363" + "23739000030151A209ABDAF1E996DFED08BF64B71356DAD8244BFAD335749206C2A1749" + "61CB15B9AD"; + + std::string kKeyRenewalResponse1 = ""; + std::string kKeyRenewalResponse2 = ""; + std::string kKeyRenewalResponse3 = "080212EC010A460A093938373635343332311208" + "6D03DC5FD3BB58031A2B596E4B58753250316A5F53785073494E6B377459354E7967396" + "755464B6963455954335A34626738366C77200128011283010801180120809A9E012880" + "9A9E0130AC02426C68747470733A2F2F6A6D7431372E676F6F676C652E636F6D2F76696" + "4656F2D6465762F6865617274626561742F63656E633F736F757263653D594F55545542" + "4526766964656F5F69643D45474843364F484E624F6F266F617574683D79612E6774737" + "1617769646576696E65483C503C1A16200342120A106B63746C0000012C697A0C870000" + "000820E3DDC78C051A20466E04142A0F410F5BA032BD8B0B77C800E9972C707E034E2ED" + "E83661C6C5386"; + + std::string kKeyReleaseServerUrl1 = "http://hamid.kir.corp.google.com:8888/drm"; + std::string kKeyReleaseServerUrl2 = "https://www.youtube.com/api/drm/widevine?video_id=03681262dc412c06&source=YOUTUBE"; + std::string kKeyReleaseServerUrl3 = "https://jmt17.google.com/video-dev/license/GetCencLicense"; +} + TEST(DeviceFilesTest, StoreCertificate) { std::string device_base_path = DeviceFiles::GetBasePath(DeviceFiles::kBasePath); @@ -93,4 +223,341 @@ TEST(DeviceFilesTest, RetrieveCertificate) { EXPECT_TRUE(File::Remove(device_base_path)); } +TEST(DeviceFilesTest, StoreLicense) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::CreateDirectory(device_base_path)); + if (File::Exists(license_path_1)) + EXPECT_TRUE(File::Remove(license_path_1)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); +} + +TEST(DeviceFilesTest, StoreLicenseInitial) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::Remove(device_base_path)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); +} + +TEST(DeviceFilesTest, StoreLicenses) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + std::string license_path_2 = device_base_path + kKeySetId2 + + DeviceFiles::kLicenseFileNameExt; + std::string license_path_3 = device_base_path + kKeySetId3 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::Remove(device_base_path)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId2, + DeviceFiles::kLicenseStateReleasing, + kPsshData2, kKeyRequest2, + kKeyResponse2, kKeyRenewalRequest2, + kKeyRenewalResponse2, + kKeyReleaseServerUrl2)); + size = sizeof(DeviceFiles::LicenseState) + kPsshData2.size() + + kKeyRequest2.size() + kKeyResponse2.size() + + kKeyRenewalRequest2.size() + kKeyRenewalResponse2.size() + + kKeyReleaseServerUrl2.size(); + EXPECT_TRUE(File::Exists(license_path_2)); + EXPECT_GT(File::FileSize(license_path_2), static_cast(size)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId3, + DeviceFiles::kLicenseStateReleasing, + kPsshData3, kKeyRequest3, + kKeyResponse3, kKeyRenewalRequest3, + kKeyRenewalResponse3, + kKeyReleaseServerUrl3)); + size = sizeof(DeviceFiles::LicenseState) + kPsshData3.size() + + kKeyRequest3.size() + kKeyResponse3.size() + + kKeyRenewalRequest3.size() + kKeyRenewalResponse3.size() + + kKeyReleaseServerUrl3.size(); + EXPECT_TRUE(File::Exists(license_path_3)); + EXPECT_GT(File::FileSize(license_path_3), static_cast(size)); +} + +TEST(DeviceFilesTest, RetrieveLicenses) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + std::string license_path_2 = device_base_path + kKeySetId2 + + DeviceFiles::kLicenseFileNameExt; + std::string license_path_3 = device_base_path + kKeySetId3 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::Remove(device_base_path)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId2, + DeviceFiles::kLicenseStateReleasing, + kPsshData2, kKeyRequest2, + kKeyResponse2, kKeyRenewalRequest2, + kKeyRenewalResponse2, + kKeyReleaseServerUrl2)); + size = sizeof(DeviceFiles::LicenseState) + kPsshData2.size() + + kKeyRequest2.size() + kKeyResponse2.size() + + kKeyRenewalRequest2.size() + kKeyRenewalResponse2.size() + + kKeyReleaseServerUrl2.size(); + EXPECT_TRUE(File::Exists(license_path_2)); + EXPECT_GT(File::FileSize(license_path_2), static_cast(size)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId3, + DeviceFiles::kLicenseStateReleasing, + kPsshData3, kKeyRequest3, + kKeyResponse3, kKeyRenewalRequest3, + kKeyRenewalResponse3, + kKeyReleaseServerUrl3)); + size = sizeof(DeviceFiles::LicenseState) + kPsshData3.size() + + kKeyRequest3.size() + kKeyResponse3.size() + + kKeyRenewalRequest3.size() + kKeyRenewalResponse3.size() + + kKeyReleaseServerUrl3.size(); + EXPECT_TRUE(File::Exists(license_path_3)); + EXPECT_GT(File::FileSize(license_path_3), static_cast(size)); + + DeviceFiles::LicenseState state; + std::string pssh_data, key_request, key_response, key_renewal_request, + key_renewal_response, release_server_url; + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId1, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateActive); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData1.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest1.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse1.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest1.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse1.data(), + key_renewal_response.size()) == 0); + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId2, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateReleasing); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData2.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest2.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse2.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest2.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse2.data(), + key_renewal_response.size()) == 0); + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId3, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateReleasing); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData3.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest3.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse3.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest3.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse3.data(), + key_renewal_response.size()) == 0); + +} + +TEST(DeviceFilesTest, UpdateLicenseState) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::Remove(device_base_path)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); + + DeviceFiles::LicenseState state; + std::string pssh_data, key_request, key_response, key_renewal_request, + key_renewal_response, release_server_url; + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId1, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateActive); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData1.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest1.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse1.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest1.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse1.data(), + key_renewal_response.size()) == 0); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateReleasing, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId1, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateReleasing); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData1.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest1.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse1.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest1.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse1.data(), + key_renewal_response.size()) == 0); +} + +TEST(DeviceFilesTest, DeleteLicense) { + std::string device_base_path = + DeviceFiles::GetBasePath(DeviceFiles::kBasePath); + std::string license_path_1 = device_base_path + kKeySetId1 + + DeviceFiles::kLicenseFileNameExt; + + if (!File::Exists(device_base_path)) + EXPECT_TRUE(File::Remove(device_base_path)); + + EXPECT_TRUE(DeviceFiles::StoreLicense(kKeySetId1, + DeviceFiles::kLicenseStateActive, + kPsshData1, kKeyRequest1, + kKeyResponse1, kKeyRenewalRequest1, + kKeyRenewalResponse1, + kKeyReleaseServerUrl1)); + size_t size = sizeof(DeviceFiles::LicenseState) + kPsshData1.size() + + kKeyRequest1.size() + kKeyResponse1.size() + + kKeyRenewalRequest1.size() + kKeyRenewalResponse1.size() + + kKeyReleaseServerUrl1.size(); + EXPECT_TRUE(File::Exists(license_path_1)); + EXPECT_GT(File::FileSize(license_path_1), static_cast(size)); + + DeviceFiles::LicenseState state; + std::string pssh_data, key_request, key_response, key_renewal_request, + key_renewal_response, release_server_url; + + EXPECT_TRUE(DeviceFiles::RetrieveLicense(kKeySetId1, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(state == DeviceFiles::kLicenseStateActive); + EXPECT_TRUE(memcmp(pssh_data.data(), kPsshData1.data(), pssh_data.size()) + == 0); + EXPECT_TRUE(memcmp(key_request.data(), kKeyRequest1.data(), + key_request.size()) == 0); + EXPECT_TRUE(memcmp(key_response.data(), kKeyResponse1.data(), + key_response.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_request.data(), kKeyRenewalRequest1.data(), + key_renewal_request.size()) == 0); + EXPECT_TRUE(memcmp(key_renewal_response.data(), kKeyRenewalResponse1.data(), + key_renewal_response.size()) == 0); + + EXPECT_TRUE(DeviceFiles::DeleteLicense(kKeySetId1)); + + EXPECT_FALSE(DeviceFiles::RetrieveLicense(kKeySetId1, &state, &pssh_data, + &key_request, &key_response, + &key_renewal_request, + &key_renewal_response, + &release_server_url)); + EXPECT_TRUE(File::Remove(device_base_path)); +} + } diff --git a/libwvdrmengine/cdm/core/test/license_request.cpp b/libwvdrmengine/cdm/core/test/license_request.cpp index 45322c29..84fd4821 100644 --- a/libwvdrmengine/cdm/core/test/license_request.cpp +++ b/libwvdrmengine/cdm/core/test/license_request.cpp @@ -44,6 +44,10 @@ void LicenseRequest::GetDrmMessage(const std::string& response, if (drm_msg_pos != std::string::npos) { drm_msg = response.substr(drm_msg_pos); } else { + // TODO(edwinwong, rfrias): hack to get HTTP message body out for + // non-Google Play webservers. Need to clean this up. Possibly test + // for GLS and decide which part is the drm message + drm_msg = response.substr(header_end_pos); LOGE("drm msg not found"); } } else { diff --git a/libwvdrmengine/cdm/core/test/url_request.cpp b/libwvdrmengine/cdm/core/test/url_request.cpp index 3512dbd3..618c2ccf 100644 --- a/libwvdrmengine/cdm/core/test/url_request.cpp +++ b/libwvdrmengine/cdm/core/test/url_request.cpp @@ -2,6 +2,8 @@ #include "url_request.h" +#include + #include "http_socket.h" #include "log.h" #include "string_conversions.h" @@ -79,7 +81,7 @@ int UrlRequest::GetStatusCode(const std::string& response) { return status_code; } -bool UrlRequest::PostRequest(const std::string& data) { +bool UrlRequest::PostRequestChunk(const std::string& data) { request_.assign("POST /"); request_.append(socket_.resource_path()); request_.append(" HTTP/1.1\r\n"); @@ -103,6 +105,31 @@ bool UrlRequest::PostRequest(const std::string& data) { return true; } +bool UrlRequest::PostRequest(const std::string& data) { + request_.assign("POST /"); + request_.append(socket_.resource_path()); + request_.append(" HTTP/1.1\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\nConnection: Keep-Alive\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("Accept-Encoding: gzip,deflate\r\n"); + request_.append("Accept-Language: en-us,fr\r\n"); + request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n"); + std::ostringstream ss; + ss << data.size(); + request_.append("Content-Length: "); + request_.append(ss.str()); + request_.append("\r\n\r\n"); + request_.append(data); + + // terminates with \r\n, then ends with an empty line + request_.append("\r\n\r\n"); + + socket_.Write(request_.c_str(), request_.size()); + return true; +} + bool UrlRequest::PostCertRequestInQueryString(const std::string& data) { request_.assign("POST /"); request_.append(socket_.resource_path()); diff --git a/libwvdrmengine/cdm/core/test/url_request.h b/libwvdrmengine/cdm/core/test/url_request.h index 0d9d4a1e..668a55fc 100644 --- a/libwvdrmengine/cdm/core/test/url_request.h +++ b/libwvdrmengine/cdm/core/test/url_request.h @@ -21,6 +21,7 @@ class UrlRequest { int GetStatusCode(const std::string& response); bool is_connected() const { return is_connected_; } bool PostRequest(const std::string& data); + bool PostRequestChunk(const std::string& data); bool PostCertRequestInQueryString(const std::string& data); private: diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 9217dd46..f6a8d9ac 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -24,6 +24,7 @@ class WvContentDecryptionModule { // Construct a valid license request. virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, @@ -32,7 +33,12 @@ class WvContentDecryptionModule { // Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmSessionId& session_id, - const CdmKeyResponse& key_data); + const CdmKeyResponse& key_data, + CdmKeySetId& key_set_id); + + // Setup keys for offline usage which were retrived in an earlier key request + virtual CdmResponseType RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id); // Cancel session virtual CdmResponseType CancelKeyRequest(const CdmSessionId& session_id); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 5171f3ca..7409b61f 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -31,28 +31,48 @@ CdmResponseType WvContentDecryptionModule::CloseSession( CdmResponseType WvContentDecryptionModule::GenerateKeyRequest( const CdmSessionId& session_id, + const CdmKeySetId& key_set_id, const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, std::string* server_url) { - CdmKeySystem key_system; - return cdm_engine_->GenerateKeyRequest(session_id, false, key_system, - init_data, license_type, - app_parameters, key_request, - server_url); + CdmResponseType sts; + if (license_type == kLicenseTypeRelease) { + sts = cdm_engine_->OpenKeySetSession(key_set_id); + if (sts != NO_ERROR) + return sts; + } + sts = cdm_engine_->GenerateKeyRequest(session_id, key_set_id, + init_data, license_type, + app_parameters, key_request, + server_url); + + if ((license_type == kLicenseTypeRelease) && (sts != KEY_MESSAGE)) { + cdm_engine_->CloseKeySetSession(key_set_id); + } + return sts; } CdmResponseType WvContentDecryptionModule::AddKey( const CdmSessionId& session_id, - const CdmKeyResponse& key_data) { - return cdm_engine_->AddKey(session_id, key_data); + const CdmKeyResponse& key_data, + CdmKeySetId& key_set_id) { + CdmResponseType sts = cdm_engine_->AddKey(session_id, key_data, key_set_id); + if ((sts == KEY_ADDED) && session_id.empty()) // license type release + cdm_engine_->CloseKeySetSession(key_set_id); + return sts; +} + +CdmResponseType WvContentDecryptionModule::RestoreKey( + const CdmSessionId& session_id, + const CdmKeySetId& key_set_id) { + return cdm_engine_->RestoreKey(session_id, key_set_id); } CdmResponseType WvContentDecryptionModule::CancelKeyRequest( const CdmSessionId& session_id) { - CdmKeySystem key_system; - return cdm_engine_->CancelKeyRequest(session_id, false, key_system); + return cdm_engine_->CancelKeyRequest(session_id); } CdmResponseType WvContentDecryptionModule::QueryStatus( diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 02b15a90..84963354 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -36,6 +36,16 @@ const std::string kProductionProvisioningServerUrl = "https://www.googleapis.com/" "certificateprovisioning/v1/devicecertificates/create" "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + +// TODO(edwinwong, rfrias): refactor to set these parameters though config +std::string kServerSdkClientAuth = ""; +wvcdm::KeyId kServerSdkKeyId = wvcdm::a2bs_hex( + "000000347073736800000000" + "edef8ba979d64acea3c827dcd51d21ed00000014" + "0801121030313233343536373839414243444546"); +std::string kServerSdkLicenseServer = "http://kir03fcpg174.widevine.net/" + "widevine/cgi-bin/drm.cgi"; + } // namespace namespace wvcdm { @@ -57,34 +67,45 @@ class WvCdmRequestLicenseTest : public testing::Test { protected: void GenerateKeyRequest(const std::string& key_system, - const std::string& init_data) { + const std::string& init_data, + CdmLicenseType license_type) { wvcdm::CdmAppParameterMap app_parameters; std::string server_url; - EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_, - init_data, - kLicenseTypeStreaming, - app_parameters, - &key_msg_, - &server_url), wvcdm::KEY_MESSAGE); + std::string key_set_id; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest(session_id_, key_set_id, init_data, + license_type, app_parameters, + &key_msg_, &server_url)); EXPECT_EQ(0, static_cast(server_url.size())); } void GenerateRenewalRequest(const std::string& key_system, - const std::string& not_used) { + CdmLicenseType license_type) { // TODO application makes a license request, CDM will renew the license // when appropriate. std::string init_data; wvcdm::CdmAppParameterMap app_parameters; std::string server_url; - EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_, - init_data, - kLicenseTypeStreaming, - app_parameters, - &key_msg_, - &server_url), wvcdm::KEY_MESSAGE); + std::string key_set_id; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest(session_id_, key_set_id, init_data, + license_type, app_parameters, + &key_msg_, &server_url)); EXPECT_NE(0, static_cast(server_url.size())); } + void GenerateKeyRelease(CdmKeySetId key_set_id) { + CdmSessionId session_id; + CdmInitData init_data; + wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; + EXPECT_EQ(wvcdm::KEY_MESSAGE, + decryptor_.GenerateKeyRequest(session_id, key_set_id, init_data, + kLicenseTypeRelease, app_parameters, + &key_msg_, &server_url)); + EXPECT_EQ(0, static_cast(server_url.size())); + } + void DumpResponse(const std::string& description, const std::string& response) { if (description.empty()) @@ -176,7 +197,13 @@ class WvCdmRequestLicenseTest : public testing::Test { return ""; } - url_request.PostRequest(key_msg_); + // TODO(edwinwong, rfrias): need a cleaner solution to handle + // HTTP servers that use chunking vs those that do not + if (server_url.compare(kServerSdkLicenseServer) == 0) + url_request.PostRequest(key_msg_); + else + url_request.PostRequestChunk(key_msg_); + std::string http_response; std::string message_body; int resp_bytes = url_request.GetResponse(http_response); @@ -248,20 +275,22 @@ class WvCdmRequestLicenseTest : public testing::Test { std::string resp = GetKeyRequestResponse(server_url, client_auth, 200); - if (is_renewal) { // TODO application makes a license request, CDM will renew the license // when appropriate - EXPECT_EQ(decryptor_.AddKey(session_id_, resp), wvcdm::KEY_ADDED); + EXPECT_EQ(decryptor_.AddKey(session_id_, resp, key_set_id_), + wvcdm::KEY_ADDED); } else { - EXPECT_EQ(decryptor_.AddKey(session_id_, resp), wvcdm::KEY_ADDED); + EXPECT_EQ(decryptor_.AddKey(session_id_, resp, key_set_id_), + wvcdm::KEY_ADDED); } } wvcdm::WvContentDecryptionModule decryptor_; - std::string key_msg_; - std::string session_id_; + CdmKeyMessage key_msg_; + CdmSessionId session_id_; + CdmKeySetId key_set_id_; }; TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) { @@ -304,7 +333,7 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningRetryTest) { TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); GetKeyRequestResponse(g_license_server, g_client_auth, 200); decryptor_.CloseSession(session_id_); } @@ -313,31 +342,76 @@ TEST_F(WvCdmRequestLicenseTest, WrongMessageTest) { decryptor_.OpenSession(g_key_system, &session_id_); std::string wrong_message = wvcdm::a2bs_hex(g_wrong_key_id); - GenerateKeyRequest(g_key_system, wrong_message); + GenerateKeyRequest(g_key_system, wrong_message, kLicenseTypeStreaming); GetKeyRequestResponse(g_license_server, g_client_auth, 500); decryptor_.CloseSession(session_id_); } -TEST_F(WvCdmRequestLicenseTest, AddKeyTest) { +TEST_F(WvCdmRequestLicenseTest, AddSteamingKeyTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); decryptor_.CloseSession(session_id_); } +TEST_F(WvCdmRequestLicenseTest, AddKeyOfflineTest) { + decryptor_.OpenSession(g_key_system, &session_id_); + GenerateKeyRequest(g_key_system, kServerSdkKeyId, kLicenseTypeOffline); + VerifyKeyRequestResponse(kServerSdkLicenseServer, kServerSdkClientAuth, + kServerSdkKeyId, false); + decryptor_.CloseSession(session_id_); +} + +TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeyTest) { + decryptor_.OpenSession(g_key_system, &session_id_); + GenerateKeyRequest(g_key_system, kServerSdkKeyId, kLicenseTypeOffline); + VerifyKeyRequestResponse(kServerSdkLicenseServer, kServerSdkClientAuth, + kServerSdkKeyId, false); + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + decryptor_.OpenSession(g_key_system, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + decryptor_.CloseSession(session_id_); +} + +TEST_F(WvCdmRequestLicenseTest, DISABLED_ReleaseOfflineKeyTest) { + decryptor_.OpenSession(g_key_system, &session_id_); + GenerateKeyRequest(g_key_system, kServerSdkKeyId, kLicenseTypeOffline); + VerifyKeyRequestResponse(kServerSdkLicenseServer, kServerSdkClientAuth, + kServerSdkKeyId, false); + CdmKeySetId key_set_id = key_set_id_; + EXPECT_FALSE(key_set_id_.empty()); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + key_set_id_.clear(); + decryptor_.OpenSession(g_key_system, &session_id_); + EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_.RestoreKey(session_id_, key_set_id)); + decryptor_.CloseSession(session_id_); + + session_id_.clear(); + key_set_id_.clear(); + GenerateKeyRelease(key_set_id); + VerifyKeyRequestResponse(kServerSdkLicenseServer, kServerSdkClientAuth, + kServerSdkKeyId, false); +} + TEST_F(WvCdmRequestLicenseTest, LicenseRenewal) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); - GenerateRenewalRequest(g_key_system, g_key_id); + GenerateRenewalRequest(g_key_system, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, true); decryptor_.CloseSession(session_id_); } TEST_F(WvCdmRequestLicenseTest, QueryKeyStatus) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); CdmQueryMap query_info; @@ -405,7 +479,7 @@ TEST_F(WvCdmRequestLicenseTest, QueryStatus) { TEST_F(WvCdmRequestLicenseTest, QueryKeyControlInfo) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); CdmQueryMap query_info; @@ -425,7 +499,7 @@ TEST_F(WvCdmRequestLicenseTest, QueryKeyControlInfo) { TEST_F(WvCdmRequestLicenseTest, ClearDecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); // key 1, clear, 256b @@ -525,7 +599,7 @@ TEST_F(WvCdmRequestLicenseTest, ClearDecryptionNoKeyTest) { TEST_F(WvCdmRequestLicenseTest, DecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); // key 1, encrypted, 256b @@ -576,7 +650,7 @@ TEST_F(WvCdmRequestLicenseTest, DecryptionTest) { TEST_F(WvCdmRequestLicenseTest, SwitchKeyDecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); uint8_t data_blocks = 2; @@ -657,7 +731,7 @@ TEST_F(WvCdmRequestLicenseTest, SwitchKeyDecryptionTest) { TEST_F(WvCdmRequestLicenseTest, PartialBlockDecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); // key 3, encrypted, 125b, offset 0 @@ -700,7 +774,7 @@ TEST_F(WvCdmRequestLicenseTest, PartialBlockDecryptionTest) { TEST_F(WvCdmRequestLicenseTest, PartialBlockWithOffsetDecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); // key 3, encrypted, 123b, offset 5 @@ -746,7 +820,7 @@ TEST_F(WvCdmRequestLicenseTest, PartialBlockWithOffsetDecryptionTest) { /* TEST_F(WvCdmRequestLicenseTest, KeyControlBlockDecryptionTest) { decryptor_.OpenSession(g_key_system, &session_id_); - GenerateKeyRequest(g_key_system, g_key_id); + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); DecryptionData data; diff --git a/libwvdrmengine/level3/arm/libwvlevel3.a b/libwvdrmengine/level3/arm/libwvlevel3.a index 05a9e4583a8ecb07bdc7c9075fb92872b6183295..b491a3c79ce6f317e09a4d85e3978c328603e4db 100644 GIT binary patch delta 8877 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 u6?1;)BcsT=haAJk`m~=~r~eNUHg?e(U?( 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 diff --git a/libwvdrmengine/level3/mips/libwvlevel3.a b/libwvdrmengine/level3/mips/libwvlevel3.a index 0a53ada1432b46e3bc2e98d66818e3713e3f693b..d1daeb6c9ed616cdefdef044388ed73a9cf2c885 100644 GIT binary patch delta 2096 zcmbuAZA?>F7{|{!En4KQSZu2zwAxCyIU!twpkIpVs8`chB!U z=ef^0_vW5U2X)sE>KYf%&B@Bj%+6Z%{nGuyah-|$|IwU3F!5jQ$PFe=9QgOv_7M}Z z>Ajnn%JvIWHSS?5Up`aY&N6kw!HyhNr^f9!6yY78XMGxj;H(HNuM!&%ebM#@lE~Af)R>Y{B!)ANK8jYcXMf^1Ddcu2NwS0xfk;3w{T6WIGShFiSXi}L?CXw;n zX_@7ihJBJ@WvyBZ>vF}imyX$4;c{~oV_G)P!dUlFHLFNu^mDSFqXxDjQLt1CXWv?b z)29}w*<~wZu262Xi7}aaN$7L4cfpbtji9-d6mns!{FX z$3@+|c-D8Ne<((rqpP9*D}6D$l-T`kM>?ZUwQiyzm~{@?)E2( z5nmxR$QtA(rJnv&+z!tW_i!6>{k(`*z~$$!;NcFDgv)-ejdC*1a&Xhnmx;lJXx$<7 zNEs&YC2vFKFyGAma18TWq2UM)^Fqp0aDy`C0TN9Zy+H|o9OFq?ev@l?4zh3ZG+v5w ziB=qvXhOS$7XuQ5n3m{6+6cjfB8dQMC92>d2u&1z9pTIQ8BBy@47X@78MzX9sFe5# zE(s$Pi6a;y2#p4s+cdEa>9@%SP$J=jgCH!c+@?Z3&flgw%g`%TS*E*Ys~+*Ar2I%7 zCG|n3yJC|b+ocMkS}OHww2shjpO(&!F6kOpyDw0(XHprFaEETwgKUW$luI<>ki=oM zOZYG#VZyY;2-5Bns!&7_nl;#amx}I#N7^=A4B0pY$cosFFy5ol4CLOU(Y>gY@WLfg z21TL@Lxd>pR>lS~yj$pya-Zs|Li&BuW26INy<6&5I7o}ZVz(2t9a4GGONw^!i9`^o zV-%3L&8;n>ofxBlGpLp-x$O96+6QSG&_#xx#;C*{Bs?J0AX}mgSSoFfPk})xfb=4+$fkCO_P)!7pm}KNR`i0pNCcZEHxfh)3emtN1=&Dk6z?D7FM2F>W{FRn59-y zkyJ$P;};X_!^-ts<-MWDWl}=Z0{>%riHt4oMsg`rG}DOmDO#}>B@%UT5QMrPC#Pt+ z>y+Lp9u>z`$~7UH1wVo6C}E8;Gwa;&7t5}AqC_s|JC&h1VofZ4jyAYI{KAZlwZfp# zNzxI||5WTt#ZrM5i^#zqi?AvE7EvW$ze*WmCK>A^MprZDC(a=Tp8GNBYZ&`Wdg2!P zD$4i`#OU`KD^)&D6T6l~KV>YILPQXObz%{(Q)1SOem--=$P-9;nlCzEizs(Sw_fRY z3SDf(gpMgibyOUf8&RR4FHeX8L&PY~Ht~B*q(|ch26e= ZEr=+^?o+*MpXt$o`rEdd9+la-_HS$vx{3e* delta 2094 zcmbuAe@v4{7{~9f1&UPYD7N^M0ktl9uV%7g>C0{b}U+qu_6O}uj-A-;{LN>$M zmzc_WhN&txF;(YcrnY{~)cZcDPf|5#_yR|daSqkZ?Bg;uJEmjwl(2(kGuc61NkItX>>Z0|Y}*ty zJ7i(3%$qCP#F)&-*a?f7HI+w~FoyV5d>U+a9*#My_>AYHccux!BF!1h^ZofTrsGUU zYdF308M_td+Ye)n$_hKz7fqE5XHb)mpEQAO#`;Dcs~ljDa>239+N&Ez-ga#KR zc4L4bJVA);qlrh6+(*`hT!|Kx6GX%Uhbhf~-}|_Zr=nY`)P;_MwFb=UC)JLGeo~tk zI&xk!V7pXq6iYR{5T|Z(0~)1EL7Q}iiyY0AEYwX(gD5vGa}IV1EAk~u0g0Vxl4wD{ zgbrc130IIPk%er6@GM2?ZQ67*>ZCo2t6r3OJ5psk9r8tf4YUQ;0ShYr|uSPDI@&+cxGGVIF$o>w1_*gbmncAyL<^h*;W46RfR-r5fVUfo z$U$!Ia`{nxOvisjx>+C#F5}=E|O+KKH4*3F?7QjZIL?hrJWiho9y; z>{HGsD&=(pu9Lzq!#o0FghvR?XO0T8@gp=>gX9r9DMPMA9?A(~_%hCp(18~z-6K3; zG8f7%A!-EuEyV%C5)#EMLG(?br&6g45XqcpDg%LHnf6)tg`@C`7`#y=jLJ#9s1N0% zN=}T35oj|fke*}KP-I#V`};)1`?MNWqcAb z_&vr_luzPB-pt^?7}HXy7_C|1BgZz293jT@B@&x(x&extaosuQ{Xo-gHB6{<6Kzfy8K URogPgdvtyNx_-Pz+iY0=FX`;FFaQ7m diff --git a/libwvdrmengine/level3/x86/libwvlevel3.a b/libwvdrmengine/level3/x86/libwvlevel3.a index b02109dcac19c6af49c1a9761c817ca2cd515b1d..28c74310c3f12d432b3f6d2ee3709a75836b3ff1 100644 GIT binary patch delta 3639 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 RSi_kaVM<@uve1x_|8G@rxjFy< delta 3569 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 J%Z%2H{{TwjkJSJG diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp index e4f6c94d..a2878641 100644 --- a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp +++ b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp @@ -47,7 +47,7 @@ class WVCryptoPluginTest : public Test { }; TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) { - MockCDM cdm; + StrictMock cdm; WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); CdmQueryMap l1Map; @@ -71,7 +71,7 @@ TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) { } TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { - MockCDM cdm; + StrictMock cdm; WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); uint8_t keyId[KEY_ID_SIZE]; @@ -182,7 +182,7 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { } TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { - MockCDM cdm; + StrictMock cdm; WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm); uint8_t keyId[KEY_ID_SIZE]; diff --git a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h index 0dc0dc7b..060ff918 100644 --- a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h @@ -50,7 +50,7 @@ class WVDrmPlugin : public android::DrmPlugin, virtual status_t closeSession(const Vector& sessionId); virtual status_t getKeyRequest( - const Vector& sessionId, + const Vector& scope, const Vector& initData, const String8& mimeType, KeyType keyType, @@ -58,11 +58,11 @@ class WVDrmPlugin : public android::DrmPlugin, Vector& request, String8& defaultUrl); - virtual status_t provideKeyResponse(const Vector& sessionId, + virtual status_t provideKeyResponse(const Vector& scope, const Vector& response, Vector& keySetId); - virtual status_t removeKeys(const Vector& keySetId); + virtual status_t removeKeys(const Vector& sessionId); virtual status_t restoreKeys(const Vector& sessionId, const Vector& keySetId); diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 1329a547..a7fc09a7 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -9,6 +9,7 @@ #include "WVDrmPlugin.h" #include +#include #include #include #include @@ -115,7 +116,7 @@ status_t WVDrmPlugin::closeSession(const Vector& sessionId) { } status_t WVDrmPlugin::getKeyRequest( - const Vector& sessionId, + const Vector& scope, const Vector& initData, const String8& mimeType, KeyType keyType, @@ -123,14 +124,20 @@ status_t WVDrmPlugin::getKeyRequest( Vector& request, String8& defaultUrl) { CdmLicenseType cdmLicenseType; + CdmSessionId cdmSessionId; + CdmKeySetId cdmKeySetId; if (keyType == kKeyType_Offline) { cdmLicenseType = kLicenseTypeOffline; + cdmSessionId.assign(scope.begin(), scope.end()); } else if (keyType == kKeyType_Streaming) { cdmLicenseType = kLicenseTypeStreaming; + cdmSessionId.assign(scope.begin(), scope.end()); + } else if (keyType == kKeyType_Release) { + cdmLicenseType = kLicenseTypeRelease; + cdmKeySetId.assign(scope.begin(), scope.end()); } else { return android::ERROR_DRM_CANNOT_HANDLE; } - CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); // Build PSSH box for PSSH data in initData. static const char psshPrefix[] = { @@ -163,8 +170,8 @@ status_t WVDrmPlugin::getKeyRequest( CdmKeyMessage keyRequest; string cdmDefaultUrl; - CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, psshBox, - cdmLicenseType, + CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmKeySetId, + psshBox, cdmLicenseType, cdmParameters, &keyRequest, &cdmDefaultUrl); @@ -177,31 +184,69 @@ status_t WVDrmPlugin::getKeyRequest( keyRequest.size()); } - return mapAndNotifyOfCdmResponseType(sessionId, res); + if (keyType == kKeyType_Release) { + // When releasing keys, we do not have a session ID. + return mapCdmResponseType(res); + } else { + // For all other requests, we have a session ID. + return mapAndNotifyOfCdmResponseType(scope, res); + } } status_t WVDrmPlugin::provideKeyResponse( - const Vector& sessionId, + const Vector& scope, const Vector& response, Vector& keySetId) { - // TODO: return keySetId for persisted offline content - CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); + CdmSessionId cdmSessionId; CdmKeyResponse cdmResponse(response.begin(), response.end()); + CdmKeySetId cdmKeySetId; - CdmResponseType res = mCDM->AddKey(cdmSessionId, cdmResponse); + bool isRequest = (memcmp(scope.array(), SESSION_ID_PREFIX.data(), + SESSION_ID_PREFIX.size()) == 0); + bool isRelease = (memcmp(scope.array(), KEY_SET_ID_PREFIX.data(), + KEY_SET_ID_PREFIX.size()) == 0); - return mapAndNotifyOfCdmResponseType(sessionId, res); + if (isRequest) { + cdmSessionId.assign(scope.begin(), scope.end()); + } else if (isRelease) { + cdmKeySetId.assign(scope.begin(), scope.end()); + } else { + return android::ERROR_DRM_CANNOT_HANDLE; + } + + CdmResponseType res = mCDM->AddKey(cdmSessionId, cdmResponse, cdmKeySetId); + + if (isRequest && isCdmResponseTypeSuccess(res)) { + keySetId.clear(); + keySetId.appendArray(reinterpret_cast(cdmKeySetId.data()), + cdmKeySetId.size()); + } + + if (isRelease) { + // When releasing keys, we do not have a session ID. + return mapCdmResponseType(res); + } else { + // For all other requests, we have a session ID. + return mapAndNotifyOfCdmResponseType(scope, res); + } } -status_t WVDrmPlugin::removeKeys(const Vector& keySetId) { - // TODO: remove persisted offline keys associated with keySetId - return android::ERROR_UNSUPPORTED; +status_t WVDrmPlugin::removeKeys(const Vector& sessionId) { + CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); + + CdmResponseType res = mCDM->CancelKeyRequest(cdmSessionId); + + return mapAndNotifyOfCdmResponseType(sessionId, res); } status_t WVDrmPlugin::restoreKeys(const Vector& sessionId, const Vector& keySetId) { - // TODO: restore persisted offline keys associated with keySetId - return android::ERROR_UNSUPPORTED; + CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); + CdmKeySetId cdmKeySetId(keySetId.begin(), keySetId.end()); + + CdmResponseType res = mCDM->RestoreKey(cdmSessionId, cdmKeySetId); + + return mapAndNotifyOfCdmResponseType(sessionId, res); } status_t WVDrmPlugin::queryKeyStatus( diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 6a21ba7d..8bd09c5a 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -3,6 +3,7 @@ // #include +#include #include #include "gmock/gmock.h" @@ -27,17 +28,22 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); - MOCK_METHOD6(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, + MOCK_METHOD7(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, + const CdmKeySetId&, const CdmInitData&, const CdmLicenseType, CdmAppParameterMap&, CdmKeyMessage*, string*)); - MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&, - const CdmKeyResponse&)); + MOCK_METHOD3(AddKey, CdmResponseType(const CdmSessionId&, + const CdmKeyResponse&, + CdmKeySetId&)); MOCK_METHOD1(CancelKeyRequest, CdmResponseType(const CdmSessionId&)); + MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&, + const CdmKeySetId&)); + MOCK_METHOD1(QueryStatus, CdmResponseType(CdmQueryMap*)); MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, @@ -111,6 +117,7 @@ class WVDrmPluginTest : public Test { fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); fclose(fp); + memcpy(sessionIdRaw, SESSION_ID_PREFIX.data(), SESSION_ID_PREFIX.size()); sessionId.appendArray(sessionIdRaw, kSessionIdSize); cdmSessionId.assign(sessionId.begin(), sessionId.end()); @@ -122,8 +129,8 @@ class WVDrmPluginTest : public Test { }; TEST_F(WVDrmPluginTest, OpensSessions) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) @@ -149,8 +156,8 @@ TEST_F(WVDrmPluginTest, OpensSessions) { } TEST_F(WVDrmPluginTest, ClosesSessions) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, CloseSession(cdmSessionId)) @@ -162,19 +169,27 @@ TEST_F(WVDrmPluginTest, ClosesSessions) { } TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kInitDataSize = 128; uint8_t initDataRaw[kInitDataSize]; static const size_t kRequestSize = 256; uint8_t requestRaw[kRequestSize]; + static const uint32_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp); fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX.data(), KEY_SET_ID_PREFIX.size()); + CdmKeySetId cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); + Vector keySetId; + keySetId.appendArray(keySetIdRaw, kKeySetIdSize); + Vector initData; initData.appendArray(initDataRaw, kInitDataSize); @@ -209,20 +224,28 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { { InSequence calls; - EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", ElementsAreArray(psshBox, kPsshBoxSize), kLicenseTypeOffline, cdmParameters, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(cdmRequest), - SetArgPointee<5>(kDefaultUrl), + .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), + SetArgPointee<6>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); - EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, + EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", ElementsAreArray(psshBox, kPsshBoxSize), kLicenseTypeStreaming, cdmParameters, _, _)) - .WillOnce(DoAll(SetArgPointee<4>(cdmRequest), - SetArgPointee<5>(kDefaultUrl), + .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), + SetArgPointee<6>(kDefaultUrl), + Return(wvcdm::KEY_MESSAGE))); + + EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, + ElementsAreArray(psshBox, kPsshBoxSize), + kLicenseTypeRelease, cdmParameters, _, + _)) + .WillOnce(DoAll(SetArgPointee<5>(cdmRequest), + SetArgPointee<6>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); } @@ -233,7 +256,6 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { String8("video/h264"), DrmPlugin::kKeyType_Offline, parameters, request, defaultUrl); - ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); @@ -241,44 +263,96 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"), DrmPlugin::kKeyType_Streaming, parameters, request, defaultUrl); + ASSERT_EQ(OK, res); + EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); + res = plugin.getKeyRequest(keySetId, initData, String8("video/h264"), + DrmPlugin::kKeyType_Release, parameters, + request, defaultUrl); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); } TEST_F(WVDrmPluginTest, AddsKeys) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kResponseSize = 256; uint8_t responseRaw[kResponseSize]; + static const uint32_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); Vector response; response.appendArray(responseRaw, kResponseSize); - // TODO: Do something with the key set ID. - Vector ignoredKeySetId; + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX.data(), KEY_SET_ID_PREFIX.size()); + CdmKeySetId cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); + Vector keySetId; - EXPECT_CALL(cdm, AddKey(cdmSessionId, ElementsAreArray(responseRaw, - kResponseSize))) - .WillOnce(Return(wvcdm::KEY_ADDED)); + Vector emptyKeySetId; - status_t res = plugin.provideKeyResponse(sessionId, response, - ignoredKeySetId); + EXPECT_CALL(cdm, AddKey(cdmSessionId, + ElementsAreArray(responseRaw, kResponseSize), _)) + .WillOnce(DoAll(SetArgReferee<2>(cdmKeySetId), + Return(wvcdm::KEY_ADDED))); + EXPECT_CALL(cdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize), + cdmKeySetId)) + .Times(1); + + status_t res = plugin.provideKeyResponse(sessionId, response, keySetId); + ASSERT_EQ(OK, res); + ASSERT_THAT(keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize)); + + res = plugin.provideKeyResponse(keySetId, response, emptyKeySetId); + ASSERT_EQ(OK, res); + EXPECT_EQ(0u, emptyKeySetId.size()); +} + +TEST_F(WVDrmPluginTest, CancelsKeyRequests) { + StrictMock cdm; + StrictMock crypto; + WVDrmPlugin plugin(&cdm, &crypto); + + EXPECT_CALL(cdm, CancelKeyRequest(cdmSessionId)) + .Times(1); + + status_t res = plugin.removeKeys(sessionId); ASSERT_EQ(OK, res); } -// TODO: Reinstate removeKeys() test once its behavior is finalized. +TEST_F(WVDrmPluginTest, RestoresKeys) { + StrictMock cdm; + StrictMock crypto; + WVDrmPlugin plugin(&cdm, &crypto); + + static const size_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; + FILE* fp = fopen("/dev/urandom", "r"); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); + fclose(fp); + + Vector keySetId; + keySetId.appendArray(keySetIdRaw, kKeySetIdSize); + + EXPECT_CALL(cdm, RestoreKey(cdmSessionId, + ElementsAreArray(keySetIdRaw, kKeySetIdSize))) + .Times(1); + + status_t res = plugin.restoreKeys(sessionId, keySetId); + ASSERT_EQ(OK, res); +} TEST_F(WVDrmPluginTest, QueriesKeyStatus) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); KeyedVector expectedLicenseStatus; @@ -293,7 +367,7 @@ TEST_F(WVDrmPluginTest, QueriesKeyStatus) { EXPECT_CALL(cdm, QueryKeyStatus(cdmSessionId, _)) .WillOnce(DoAll(SetArgPointee<1>(cdmLicenseStatus), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); KeyedVector licenseStatus; @@ -310,8 +384,8 @@ TEST_F(WVDrmPluginTest, QueriesKeyStatus) { } TEST_F(WVDrmPluginTest, GetsProvisioningRequests) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kRequestSize = 256; @@ -340,8 +414,8 @@ TEST_F(WVDrmPluginTest, GetsProvisioningRequests) { } TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kResponseSize = 512; @@ -363,8 +437,8 @@ TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) { } TEST_F(WVDrmPluginTest, GetsSecureStops) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kStopSize = 53; @@ -405,8 +479,8 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) { } TEST_F(WVDrmPluginTest, ReleasesSecureStops) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kMessageSize = 128; @@ -428,8 +502,8 @@ TEST_F(WVDrmPluginTest, ReleasesSecureStops) { } TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); CdmQueryMap l1Map; @@ -493,8 +567,8 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { } TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); String8 stringResult; @@ -512,8 +586,8 @@ TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) { } TEST_F(WVDrmPluginTest, DoesNotSetProperties) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kValueSize = 32; @@ -534,8 +608,8 @@ TEST_F(WVDrmPluginTest, DoesNotSetProperties) { } TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); Vector keyId; @@ -548,7 +622,7 @@ TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -591,8 +665,8 @@ MATCHER_P(IsIV, iv, "") { } TEST_F(WVDrmPluginTest, CallsGenericEncrypt) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kDataSize = 256; @@ -631,7 +705,7 @@ TEST_F(WVDrmPluginTest, CallsGenericEncrypt) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -655,8 +729,8 @@ TEST_F(WVDrmPluginTest, CallsGenericEncrypt) { } TEST_F(WVDrmPluginTest, CallsGenericDecrypt) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kDataSize = 256; @@ -695,7 +769,7 @@ TEST_F(WVDrmPluginTest, CallsGenericDecrypt) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -719,8 +793,8 @@ TEST_F(WVDrmPluginTest, CallsGenericDecrypt) { } TEST_F(WVDrmPluginTest, CallsGenericSign) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kDataSize = 256; @@ -761,7 +835,7 @@ TEST_F(WVDrmPluginTest, CallsGenericSign) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -785,8 +859,8 @@ TEST_F(WVDrmPluginTest, CallsGenericSign) { } TEST_F(WVDrmPluginTest, CallsGenericVerify) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kDataSize = 256; @@ -837,7 +911,7 @@ TEST_F(WVDrmPluginTest, CallsGenericVerify) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -866,8 +940,8 @@ TEST_F(WVDrmPluginTest, CallsGenericVerify) { } TEST_F(WVDrmPluginTest, RegistersForEvents) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, AttachEventListener(cdmSessionId, &plugin)) @@ -877,7 +951,7 @@ TEST_F(WVDrmPluginTest, RegistersForEvents) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) @@ -892,8 +966,8 @@ TEST_F(WVDrmPluginTest, RegistersForEvents) { } TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; { WVDrmPlugin plugin(&cdm, &crypto); @@ -939,8 +1013,8 @@ TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) { } TEST_F(WVDrmPluginTest, MarshalsEvents) { - MockCDM cdm; - MockCrypto crypto; + StrictMock cdm; + StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); sp listener = new MockDrmPluginListener(); @@ -965,7 +1039,7 @@ TEST_F(WVDrmPluginTest, MarshalsEvents) { EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId), - Return(wvcdm::NO_ERROR))); + Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 6b7885f0..a3d70e51 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -323,8 +323,8 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message, } bool SessionContext::ParseKeyControl( - const std::vector& key_control_string, - KeyControlBlock& key_control_block) { + const std::vector& key_control_string, + KeyControlBlock& key_control_block) { key_control_block.Invalidate(); @@ -337,15 +337,6 @@ bool SessionContext::ParseKeyControl( return false; } - if (!key_control_block.Validate()) { - LOGE("KCB: BAD Signature"); - return false; - } - if (!CheckNonce(key_control_block.nonce())) { - LOGE("KCB: BAD Nonce"); - return false; - } - LOGD("KCB:"); LOGD(" valid: %d", key_control_block.valid()); LOGD(" duration: %d", key_control_block.duration()); @@ -375,6 +366,16 @@ bool SessionContext::ParseKeyControl( const char* cgms_values[4] = {"free", "BAD", "once", "never"}; LOGD(" CGMS = %s", cgms_values[cgms_bits]); + if (!key_control_block.Validate()) { + LOGE("KCB: BAD Signature"); + return false; + } + if ((key_control_block.control_bits() & kControlNonceEnabled) + && (!CheckNonce(key_control_block.nonce()))) { + LOGE("KCB: BAD Nonce"); + return false; + } + return true; } diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_keybox_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_keybox_test.cpp deleted file mode 100644 index 06d345d3..00000000 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_keybox_test.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2013 Google Inc. All Rights Reserved. - -// -// OEMCrypto unit tests -// -#include -#include -#include -#include -#include - -#include "OEMCryptoCENC.h" -#include "string_conversions.h" -#include "wv_cdm_constants.h" -#include "wv_keybox.h" - -using namespace std; - -namespace wvoec { - -static wvoec_mock::WidevineKeybox kValidKeybox02 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 - 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, - 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, - 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, - 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, - 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, - 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, - 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, - 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, - 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0x2a, 0x3b, 0x3e, 0xe4, - } -}; - -static wvoec_mock::WidevineKeybox kValidKeybox03 = { - // Sample keybox used for test vectors - { - // deviceID - 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 - 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ - }, { - // key - 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, - 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, - }, { - // data - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, - 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, - 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, - 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, - 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, - 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, - 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, - 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, - 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, - }, { - // magic - 0x6b, 0x62, 0x6f, 0x78, - }, { - // Crc - 0xa1, 0x99, 0x5f, 0x46, - } -}; - -// Define CAN_INSTALL_KEYBOX if you are compiling with the reference -// implementation of OEMCrypto, or if your version of OEMCrypto supports -// OEMCrypto_InstallKeybox and OEMCrypto_WrapKeybox. -#if defined(CAN_INSTALL_KEYBOX) -// The Below tests are based on a specific keybox which is installed for testing. - -class OEMCryptoKeyboxTest : public ::testing::Test { - - protected: - virtual void SetUp() { - } - - void install_keybox(wvoec_mock::WidevineKeybox& keybox, bool good) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()) - << "OEMCrypto_Initialize failed."; - OEMCryptoResult sts; - uint8_t wrapped[sizeof(wvoec_mock::WidevineKeybox)]; - size_t length = sizeof(wvoec_mock::WidevineKeybox); - sts = OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), - sizeof(keybox), - wrapped, - &length, - NULL, 0); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox)); - if( good ) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } else { - // Can return error now, or return error on IsKeyboxValid. - } - } - - virtual void TearDown() { - OEMCrypto_Terminate(); - } - public: - -}; - -TEST_F(OEMCryptoKeyboxTest, DefaultKeybox) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()) - << "OEMCrypto_Initialize failed."; - OEMCryptoResult sts; - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); -} - -TEST_F(OEMCryptoKeyboxTest, GoodKeybox) { - wvoec_mock::WidevineKeybox keybox = kValidKeybox02; - OEMCryptoResult sts; - install_keybox(keybox, true); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - keybox = kValidKeybox03; - install_keybox(keybox, true); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); -} - -TEST_F(OEMCryptoKeyboxTest, BadCRCKeybox) { - wvoec_mock::WidevineKeybox keybox = kValidKeybox02; - keybox.crc_[1] = 42; - OEMCryptoResult sts; - install_keybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); -} - -TEST_F(OEMCryptoKeyboxTest, BadMagicKeybox) { - wvoec_mock::WidevineKeybox keybox = kValidKeybox02; - keybox.magic_[1] = 42; - OEMCryptoResult sts; - install_keybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); -} - - -TEST_F(OEMCryptoKeyboxTest, BadDataKeybox) { - wvoec_mock::WidevineKeybox keybox = kValidKeybox02; - keybox.data_[1] = 42; - OEMCryptoResult sts; - install_keybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); -} - -#endif // CAN_INSTALL_KEYBOX - -} // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 9f6540a1..16463919 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -934,9 +934,9 @@ class Session { enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); } - void LoadTestKeys(uint32_t duration, uint32_t control) { + void LoadTestKeys(uint32_t duration, uint32_t control, uint32_t nonce) { MessageData data; - FillSimpleMessage(&data, duration, control); + FillSimpleMessage(&data, duration, control, nonce); MessageData encrypted; EncryptMessage(data, &encrypted); std::vector signature; @@ -1059,7 +1059,8 @@ class Session { } } - void FillSimpleMessage(MessageData* data, uint32_t duration, uint32_t control) { + void FillSimpleMessage(MessageData* data, uint32_t duration, uint32_t control, + uint32_t nonce) { OEMCrypto_GetRandom(data->mac_key_iv, sizeof(data->mac_key_iv)); OEMCrypto_GetRandom(data->mac_keys, sizeof(data->mac_keys)); for (unsigned int i = 0; i < kNumKeys; i++) { @@ -1072,7 +1073,7 @@ class Session { sizeof(data->keys[i].control_iv)); memcpy(data->keys[i].control.verification, "kctl", 4); data->keys[i].control.duration = htonl(duration); - data->keys[i].control.nonce = htonl(nonce_); + data->keys[i].control.nonce = htonl(nonce); data->keys[i].control.control_bits = htonl(control); } // For the canned decryption content, The first key is: @@ -1918,7 +1919,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyNoNonce) { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 42); s.close(); testTearDown(); } @@ -1930,7 +1931,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithNonce) { s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(0, wvoec_mock::kControlNonceEnabled); + s.LoadTestKeys(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); s.close(); testTearDown(); } @@ -1946,7 +1947,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange1) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -1978,7 +1979,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange2) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2011,7 +2012,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange3) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2045,7 +2046,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange4) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2079,7 +2080,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange5) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2113,7 +2114,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange6) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2147,7 +2148,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange7) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2181,12 +2182,8 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); - data.keys[0].control.control_bits = htonl(wvoec_mock::kControlNonceEnabled); - data.keys[1].control.control_bits = htonl(wvoec_mock::kControlNonceEnabled); - data.keys[2].control.control_bits = htonl(wvoec_mock::kControlNonceEnabled); - data.keys[1].control.nonce = 42; // This one is bad. - + s.FillSimpleMessage(&data, 0, wvoec_mock::kControlNonceEnabled, + 42); // bad nonce. MessageData encrypted; s.EncryptMessage(data, &encrypted); std::vector signature; @@ -2218,7 +2215,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); data.keys[1].control.verification[2] = 'Z'; MessageData encrypted; @@ -2252,7 +2249,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeysBadSignature) { s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2287,7 +2284,7 @@ TEST_F(DISABLED_TestKeybox, LoadKeysWithNoDerivedKeys) { // s.GenerateDerivedKeys(); MessageData data; - s.FillSimpleMessage(&data, 0, 0); + s.FillSimpleMessage(&data, 0, 0, 0); MessageData encrypted; s.EncryptMessage(data, &encrypted); @@ -2321,11 +2318,10 @@ class DISABLED_RefreshKeyTest : public DISABLED_TestKeybox { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); uint32_t nonce; s.GenerateNonce(&nonce); - s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, - true); + s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, true); s.close(); } @@ -2333,7 +2329,7 @@ class DISABLED_RefreshKeyTest : public DISABLED_TestKeybox { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 0); uint32_t nonce; s.GenerateNonce(&nonce); s.RefreshTestKeys(key_count,0, 0, true); @@ -2344,7 +2340,7 @@ class DISABLED_RefreshKeyTest : public DISABLED_TestKeybox { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); uint32_t nonce = s.get_nonce(); s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, false); @@ -2354,7 +2350,7 @@ class DISABLED_RefreshKeyTest : public DISABLED_TestKeybox { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); uint32_t nonce; s.GenerateNonce(&nonce); nonce = 42; @@ -2395,7 +2391,7 @@ TEST_F(DISABLED_TestKeybox, Decrypt) { s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -2452,7 +2448,7 @@ TEST_F(DISABLED_TestKeybox, DecryptZeroDuration) { s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(0, 0); + s.LoadTestKeys(0, 0, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -2509,7 +2505,7 @@ TEST_F(DISABLED_TestKeybox, DecryptWithOffset) { s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -2568,7 +2564,7 @@ TEST_F(DISABLED_TestKeybox, DecryptUnencrypted) { s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -2658,7 +2654,7 @@ TEST_F(DISABLED_TestKeybox, DecryptSecureToClear) { s.open(); s.GenerateDerivedKeys(); s.LoadTestKeys(kDuration, wvoec_mock::kControlObserveDataPath - | wvoec_mock::kControlDataPathSecure); + | wvoec_mock::kControlDataPathSecure, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -2714,7 +2710,7 @@ TEST_F(DISABLED_TestKeybox, KeyDuration) { Session& s = createSession("ONE"); s.open(); s.GenerateDerivedKeys(); - s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -3168,7 +3164,7 @@ TEST_F(DISABLED_TestKeybox, CertificateDecrypt) { s.open(); s.InstallRSASessionTestKey(wrapped_rsa_key); - s.LoadTestKeys(kDuration, 0); + s.LoadTestKeys(kDuration, 0, 0); // Select the key (from FillSimpleMessage) vector keyId = wvcdm::a2b_hex("000000000000000000000000"); @@ -3228,7 +3224,7 @@ class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { void MakeFourKeys(Session* s) { - s->FillSimpleMessage(&message_data_, kDuration, 0); + s->FillSimpleMessage(&message_data_, kDuration, 0, 0); message_data_.keys[0].control.control_bits = htonl(wvoec_mock::kControlAllowEncrypt); message_data_.keys[1].control.control_bits = htonl(wvoec_mock::kControlAllowDecrypt); message_data_.keys[2].control.control_bits = htonl(wvoec_mock::kControlAllowSign);