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
This commit is contained in:
Jeff Tinker
2013-05-15 19:23:36 -07:00
parent 898d870126
commit 1b295f4c81
30 changed files with 1647 additions and 471 deletions

View File

@@ -14,6 +14,7 @@ class CryptoEngine;
class WvCdmEventListener;
typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
typedef std::map<CdmKeySetId, CdmSessionId> 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_;

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,8 @@
#include <string>
#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"

View File

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

View File

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

View File

@@ -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<CdmSessionId,CdmSession*>::const_iterator CdmSessionIter;
typedef std::map<CdmSessionId, CdmSession*>::const_iterator CdmSessionIter;
typedef std::map<CdmKeySetId, CdmSessionId>::iterator CdmReleaseKeySetIter;
CdmEngine::CdmEngine() : provisioning_session_(NULL) {
Properties::Init();
Clock clock;
srand(static_cast<int>(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<uint8_t> 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<uint8_t> 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

View File

@@ -5,12 +5,14 @@
#include <iostream>
#include <sstream>
#include <stdlib.h>
#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<uint8_t> hash(SHA256_DIGEST_LENGTH, 0);
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, pssh_data.data(), pssh_data.size());
SHA256_Update(&sha256, &current_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<CdmEventListenerIter, bool> 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);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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));
}
}

View File

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

View File

@@ -2,6 +2,8 @@
#include "url_request.h"
#include <sstream>
#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());

View File

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

View File

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

View File

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

View File

@@ -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<int>(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<int>(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<int>(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;