Source release v3.1.0

This commit is contained in:
Gene Morgan
2016-07-19 18:43:15 -07:00
parent 7a7f78d654
commit 643b91b616
108 changed files with 16537 additions and 7174 deletions

View File

@@ -15,7 +15,6 @@
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "scoped_ptr.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -55,11 +54,13 @@ class UsagePropertySet : public CdmClientPropertySet {
bool CdmEngine::seeded_ = false;
CdmEngine::CdmEngine()
CdmEngine::CdmEngine(FileSystem* file_system)
: cert_provisioning_(NULL),
cert_provisioning_requested_security_level_(kLevelDefault),
file_system_(file_system),
usage_session_(NULL),
last_usage_information_update_time_(0) {
assert(file_system);
Properties::Init();
if (!seeded_) {
Clock clock;
@@ -79,7 +80,22 @@ CdmEngine::~CdmEngine() {
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
const std::string& origin,
const CdmSessionId& forced_session_id,
WvCdmEventListener* event_listener) {
return OpenSession(key_system, property_set, event_listener,
&forced_session_id, NULL);
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
WvCdmEventListener* event_listener,
CdmSessionId* session_id) {
return OpenSession(key_system, property_set, event_listener, NULL,
session_id);
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
CdmClientPropertySet* property_set,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id,
CdmSessionId* session_id) {
@@ -90,8 +106,8 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
return INVALID_KEY_SYSTEM;
}
if (!session_id) {
LOGE("CdmEngine::OpenSession: no session ID destination provided");
if (!session_id && !forced_session_id) {
LOGE("CdmEngine::OpenSession: no (forced/)session ID destination provided");
return INVALID_PARAMETERS_ENG_1;
}
@@ -101,32 +117,33 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
}
}
scoped_ptr<CdmSession> new_session(
new CdmSession(property_set, origin, event_listener, forced_session_id));
if (new_session->session_id().empty()) {
LOGE("CdmEngine::OpenSession: failure to generate session ID");
return EMPTY_SESSION_ID;
}
scoped_ptr<CdmSession> new_session(new CdmSession(file_system_));
CdmResponseType sts = new_session->Init();
CdmResponseType sts = new_session->Init(property_set, forced_session_id,
event_listener);
if (sts != NO_ERROR) {
if (sts == NEED_PROVISIONING) {
cert_provisioning_requested_security_level_ =
new_session->GetRequestedSecurityLevel();
// Reserve a session ID so the CDM can return success.
if (session_id)
*session_id = new_session->GenerateSessionId();
} else {
LOGE("CdmEngine::OpenSession: bad session init: %d", sts);
}
return sts;
}
*session_id = new_session->session_id();
CdmSessionId id = new_session->session_id();
AutoLock lock(session_list_lock_);
sessions_[*session_id] = new_session.release();
sessions_[id] = new_session.release();
if (session_id) *session_id = id;
return NO_ERROR;
}
CdmResponseType CdmEngine::OpenKeySetSession(
const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set,
const std::string& origin, WvCdmEventListener* event_listener) {
WvCdmEventListener* event_listener) {
LOGI("CdmEngine::OpenKeySetSession");
if (key_set_id.empty()) {
@@ -135,9 +152,8 @@ CdmResponseType CdmEngine::OpenKeySetSession(
}
CdmSessionId session_id;
CdmResponseType sts =
OpenSession(KEY_SYSTEM, property_set, origin, event_listener,
NULL /* forced_session_id */, &session_id);
CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener,
NULL /* forced_session_id */, &session_id);
if (sts != NO_ERROR) return sts;
@@ -182,9 +198,7 @@ bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) {
CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
const InitializationData& init_data, const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id_out) {
CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) {
LOGI("CdmEngine::GenerateKeyRequest");
CdmSessionId id = session_id;
@@ -223,11 +237,11 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
}
if (!key_request) {
LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided");
LOGE("CdmEngine::GenerateKeyRequest: output destination provided");
return INVALID_PARAMETERS_ENG_2;
}
key_request->clear();
key_request->message.clear();
if (license_type == kLicenseTypeRelease &&
!iter->second->license_received()) {
@@ -240,8 +254,7 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
}
sts = iter->second->GenerateKeyRequest(
init_data, license_type, app_parameters, key_request, key_request_type,
server_url, key_set_id_out);
init_data, license_type, app_parameters, key_request);
if (KEY_MESSAGE != sts) {
if (sts == NEED_PROVISIONING) {
@@ -300,7 +313,10 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
return EMPTY_KEY_DATA_1;
}
CdmResponseType sts = iter->second->AddKey(key_data, key_set_id);
CdmResponseType sts = iter->second->AddKey(key_data);
if (key_set_id) {
*key_set_id = iter->second->key_set_id();
}
switch (sts) {
case KEY_ADDED:
@@ -359,8 +375,7 @@ CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
}
CdmResponseType CdmEngine::GenerateRenewalRequest(
const CdmSessionId& session_id, CdmKeyMessage* key_request,
std::string* server_url) {
const CdmSessionId& session_id, CdmKeyRequest* key_request) {
LOGI("CdmEngine::GenerateRenewalRequest");
CdmSessionMap::iterator iter = sessions_.find(session_id);
@@ -371,14 +386,13 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
}
if (!key_request) {
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination");
LOGE("CdmEngine::GenerateRenewalRequest: no request destination");
return INVALID_PARAMETERS_ENG_4;
}
key_request->clear();
key_request->message.clear();
CdmResponseType sts =
iter->second->GenerateRenewalRequest(key_request, server_url);
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request);
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d",
@@ -414,8 +428,8 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
}
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
const std::string& key,
std::string* value) {
const std::string& query_token,
std::string* query_response) {
LOGI("CdmEngine::QueryStatus");
CryptoSession crypto_session;
if (security_level == kLevel3) {
@@ -423,36 +437,41 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
if (NO_ERROR != status) return INVALID_QUERY_STATUS;
}
if (key == QUERY_KEY_SECURITY_LEVEL) {
if (!query_response) {
LOGE("CdmEngine::QueryStatus: no query response destination");
return INVALID_PARAMETERS_ENG_6;
}
if (query_token == QUERY_KEY_SECURITY_LEVEL) {
CdmSecurityLevel security_level = crypto_session.GetSecurityLevel();
switch (security_level) {
case kSecurityLevelL1:
*value = QUERY_VALUE_SECURITY_LEVEL_L1;
*query_response = QUERY_VALUE_SECURITY_LEVEL_L1;
break;
case kSecurityLevelL2:
*value = QUERY_VALUE_SECURITY_LEVEL_L2;
*query_response = QUERY_VALUE_SECURITY_LEVEL_L2;
break;
case kSecurityLevelL3:
*value = QUERY_VALUE_SECURITY_LEVEL_L3;
*query_response = QUERY_VALUE_SECURITY_LEVEL_L3;
break;
case kSecurityLevelUninitialized:
case kSecurityLevelUnknown:
*value = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
*query_response = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
LOGW("CdmEngine::QueryStatus: Unknown security level: %d",
security_level);
return UNKNOWN_ERROR;
}
} else if (key == QUERY_KEY_DEVICE_ID) {
} else if (query_token == QUERY_KEY_DEVICE_ID) {
std::string deviceId;
if (!crypto_session.GetDeviceUniqueId(&deviceId)) {
LOGW("CdmEngine::QueryStatus: GetDeviceUniqueId failed");
return UNKNOWN_ERROR;
}
*value = deviceId;
} else if (key == QUERY_KEY_SYSTEM_ID) {
*query_response = deviceId;
} else if (query_token == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
if (!crypto_session.GetSystemId(&system_id)) {
LOGW("CdmEngine::QueryStatus: GetSystemId failed");
@@ -461,35 +480,36 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
std::ostringstream system_id_stream;
system_id_stream << system_id;
*value = system_id_stream.str();
} else if (key == QUERY_KEY_PROVISIONING_ID) {
*query_response = system_id_stream.str();
} else if (query_token == QUERY_KEY_PROVISIONING_ID) {
std::string provisioning_id;
if (!crypto_session.GetProvisioningId(&provisioning_id)) {
LOGW("CdmEngine::QueryStatus: GetProvisioningId failed");
return UNKNOWN_ERROR;
}
*value = provisioning_id;
} else if (key == QUERY_KEY_CURRENT_HDCP_LEVEL ||
key == QUERY_KEY_MAX_HDCP_LEVEL) {
*query_response = provisioning_id;
} else if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ||
query_token == QUERY_KEY_MAX_HDCP_LEVEL) {
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
if (!crypto_session.GetHdcpCapabilities(&current_hdcp, &max_hdcp)) {
LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed");
return UNKNOWN_ERROR;
}
*value = MapHdcpVersion(key == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp
: max_hdcp);
} else if (key == QUERY_KEY_USAGE_SUPPORT) {
*query_response =
MapHdcpVersion(query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ?
current_hdcp : max_hdcp);
} else if (query_token == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
if (!crypto_session.UsageInformationSupport(&supports_usage_reporting)) {
LOGW("CdmEngine::QueryStatus: UsageInformationSupport failed");
return UNKNOWN_ERROR;
}
*value = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else if (key == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
*query_response =
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
size_t number_of_open_sessions;
if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) {
LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed");
@@ -498,8 +518,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
std::ostringstream open_sessions_stream;
open_sessions_stream << number_of_open_sessions;
*value = open_sessions_stream.str();
} else if (key == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
*query_response = open_sessions_stream.str();
} else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
size_t maximum_number_of_sessions;
if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) {
LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed");
@@ -508,8 +528,8 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
std::ostringstream max_sessions_stream;
max_sessions_stream << maximum_number_of_sessions;
*value = max_sessions_stream.str();
} else if (key == QUERY_KEY_OEMCRYPTO_API_VERSION) {
*query_response = max_sessions_stream.str();
} else if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) {
uint32_t api_version;
if (!crypto_session.GetApiVersion(&api_version)) {
LOGW("CdmEngine::QueryStatus: GetApiVersion failed");
@@ -518,10 +538,10 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
std::ostringstream api_version_stream;
api_version_stream << api_version;
*value = api_version_stream.str();
*query_response = api_version_stream.str();
} else {
LOGW("CdmEngine::QueryStatus: Unknown status requested, key = %s",
key.c_str());
LOGW("CdmEngine::QueryStatus: Unknown status requested, token = %s",
query_token.c_str());
return INVALID_QUERY_KEY;
}
@@ -529,7 +549,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
}
CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info) {
CdmQueryMap* query_response) {
LOGI("CdmEngine::QuerySessionStatus");
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
@@ -537,7 +557,7 @@ CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
session_id.c_str());
return SESSION_NOT_FOUND_8;
}
return iter->second->QueryStatus(key_info);
return iter->second->QueryStatus(query_response);
}
bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) {
@@ -563,7 +583,7 @@ bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) {
}
CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info) {
CdmQueryMap* query_response) {
LOGI("CdmEngine::QueryKeyStatus");
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
@@ -571,19 +591,72 @@ CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
session_id.c_str());
return SESSION_NOT_FOUND_9;
}
return iter->second->QueryKeyStatus(key_info);
return iter->second->QueryKeyStatus(query_response);
}
CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id,
CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryKeyControlInfo");
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id,
const std::string& key_id,
CdmKeyAllowedUsage* key_usage) {
LOGI("CdmEngine::QueryKeyAllowedUsage");
if (!key_usage) {
LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination");
return INVALID_PARAMETERS_ENG_12;
}
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s",
LOGE("CdmEngine::QueryKeyAllowedUsage: session_id not found = %s",
session_id.c_str());
return SESSION_NOT_FOUND_12;
}
return iter->second->QueryKeyAllowedUsage(key_id, key_usage);
}
CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id,
CdmKeyAllowedUsage* key_usage) {
LOGI("CdmEngine::QueryKeyAllowedUsage (all sessions)");
CdmResponseType session_sts;
CdmKeyAllowedUsage found_in_this_session;
bool found = false;
if (!key_usage) {
LOGE("CdmEngine::QueryKeyAllowedUsage: no response destination");
return INVALID_PARAMETERS_ENG_7;
}
key_usage->Clear();
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
session_sts = iter->second->QueryKeyAllowedUsage(key_id,
&found_in_this_session);
if (session_sts == NO_ERROR) {
if (found) {
// Found another key. If usage settings do not match, fail.
if (!key_usage->Equals(found_in_this_session)) {
key_usage->Clear();
return KEY_CONFLICT_1;
}
} else {
*key_usage = found_in_this_session;
found = true;
}
} else if (session_sts != KEY_NOT_FOUND_1) {
LOGE("CdmEngine::QueryKeyAllowedUsage (all sessions) FAILED = %d",
session_sts);
key_usage->Clear();
return session_sts;
}
}
return (found) ? NO_ERROR : KEY_NOT_FOUND_2;
}
CdmResponseType CdmEngine::QueryOemCryptoSessionId(
const CdmSessionId& session_id, CdmQueryMap* query_response) {
LOGI("CdmEngine::QueryOemCryptoSessionId");
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryOemCryptoSessionId: session_id not found = %s",
session_id.c_str());
return SESSION_NOT_FOUND_10;
}
return iter->second->QueryKeyControlInfo(key_info);
return iter->second->QueryOemCryptoSessionId(query_response);
}
/*
@@ -595,8 +668,7 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id,
*/
CdmResponseType CdmEngine::GetProvisioningRequest(
CdmCertificateType cert_type, const std::string& cert_authority,
const std::string& origin, CdmProvisioningRequest* request,
std::string* default_url) {
CdmProvisioningRequest* request, std::string* default_url) {
if (!request) {
LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters");
return INVALID_PROVISIONING_REQUEST_PARAM_1;
@@ -613,7 +685,7 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
}
CdmResponseType ret = cert_provisioning_->GetProvisioningRequest(
cert_provisioning_requested_security_level_, cert_type, cert_authority,
origin, request, default_url);
file_system_->origin(), request, default_url);
if (ret != NO_ERROR) {
cert_provisioning_.reset(NULL); // Release resources.
}
@@ -628,8 +700,8 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
* Returns NO_ERROR for success and CdmResponseType error code if fails.
*/
CdmResponseType CdmEngine::HandleProvisioningResponse(
const std::string& origin, const CdmProvisioningResponse& response,
std::string* cert, std::string* wrapped_key) {
const CdmProvisioningResponse& response, std::string* cert,
std::string* wrapped_key) {
if (response.empty()) {
LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response.");
cert_provisioning_.reset(NULL);
@@ -661,7 +733,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
return EMPTY_PROVISIONING_CERTIFICATE_2;
}
CdmSecurityLevel security_level = crypto_session.GetSecurityLevel();
if (!IsProvisioned(security_level, origin)) {
if (!IsProvisioned(security_level)) {
LOGE(
"CdmEngine::HandleProvisioningResponse: provisioning object "
"missing.");
@@ -671,7 +743,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
}
CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
origin, response, cert, wrapped_key);
file_system_, response, cert, wrapped_key);
// Release resources only on success. It is possible that a provisioning
// attempt was made after this one was requested but before the response was
// received, which will cause this attempt to fail. Not releasing will
@@ -680,28 +752,25 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
return ret;
}
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level,
const std::string& origin) {
DeviceFiles handle;
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("CdmEngine::IsProvisioned: unable to initialize device files");
return false;
}
return handle.HasCertificate(origin);
return handle.HasCertificate();
}
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level,
const std::string& origin) {
DeviceFiles handle;
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("CdmEngine::Unprovision: unable to initialize device files");
return UNPROVISION_ERROR_1;
}
if (origin != EMPTY_ORIGIN) {
if (!handle.RemoveCertificate(origin)) {
LOGE("CdmEngine::Unprovision: unable to delete certificate for origin %s",
origin.c_str());
if (!file_system_->origin().empty()) {
if (!handle.RemoveCertificate()) {
LOGE("CdmEngine::Unprovision: unable to delete certificate");
return UNPROVISION_ERROR_2;
}
return NO_ERROR;
@@ -732,16 +801,19 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
if (NULL == usage_property_set_.get()) {
usage_property_set_.reset(new UsagePropertySet());
}
if (!usage_info) {
LOGE("CdmEngine::GetUsageInfo: no usage info destination");
return INVALID_PARAMETERS_ENG_8;
}
usage_property_set_->set_security_level(kLevelDefault);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
CdmResponseType status = usage_session_->Init();
usage_session_.reset(new CdmSession(file_system_));
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
DeviceFiles handle;
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: device file init error");
return GET_USAGE_INFO_ERROR_1;
@@ -753,9 +825,8 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
&license_response)) {
usage_property_set_->set_security_level(kLevel3);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
status = usage_session_->Init();
usage_session_.reset(new CdmSession(file_system_));
status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
@@ -771,18 +842,20 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
}
}
std::string server_url;
usage_info->resize(1);
status =
usage_session_->RestoreUsageSession(license_request, license_response);
usage_session_->RestoreUsageSession(license_request,license_response);
if (KEY_ADDED != status) {
LOGE("CdmEngine::GetUsageInfo: restore usage session error %d", status);
usage_info->clear();
return status;
}
status =
usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url);
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
usage_info->clear();
usage_info->push_back(request.message);
if (KEY_MESSAGE != status) {
LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", status);
@@ -797,6 +870,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
// Return a random usage report from a random security level
SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3;
CdmResponseType status = UNKNOWN_ERROR;
if (!usage_info) {
LOGE("CdmEngine::GetUsageInfo: no usage info destination");
return INVALID_PARAMETERS_ENG_9;
}
do {
status = GetUsageInfo(app_id, security_level, usage_info);
@@ -822,16 +899,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
usage_property_set_->set_security_level(requested_security_level);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
usage_session_.reset(new CdmSession(file_system_));
CdmResponseType status = usage_session_->Init();
CdmResponseType status = usage_session_->Init(usage_property_set_.get());
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
DeviceFiles handle;
DeviceFiles handle(file_system_);
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: unable to initialize device files");
return GET_USAGE_INFO_ERROR_3;
@@ -843,12 +919,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
return GET_USAGE_INFO_ERROR_4;
}
if (!usage_info) {
LOGE("CdmEngine::GetUsageInfo: no usage info destination");
return INVALID_PARAMETERS_ENG_10;
}
if (0 == license_info.size()) {
usage_info->resize(0);
return NO_ERROR;
}
std::string server_url;
usage_info->resize(kUsageReportsPerRequest);
uint32_t index = rand() % license_info.size();
@@ -861,8 +940,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
return status;
}
status =
usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url);
CdmKeyRequest request;
status = usage_session_->GenerateReleaseRequest(&request);
usage_info->clear();
usage_info->push_back(request.message);
switch (status) {
case KEY_MESSAGE:
@@ -888,7 +970,7 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
CdmResponseType status = NO_ERROR;
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
DeviceFiles handle;
DeviceFiles handle(file_system_);
if (handle.Init(static_cast<CdmSecurityLevel>(j))) {
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(app_id, &provider_session_tokens)) {
@@ -901,9 +983,8 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
? kLevel3
: kLevelDefault;
usage_property_set_->set_security_level(security_level);
usage_session_.reset(
new CdmSession(usage_property_set_.get(),
EMPTY_ORIGIN, NULL, NULL));
usage_session_.reset(new CdmSession(file_system_));
usage_session_->Init(usage_property_set_.get());
CdmResponseType status2 = usage_session_->
DeleteMultipleUsageInformation(provider_session_tokens);
if (status2 != NO_ERROR) {
@@ -954,7 +1035,12 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
return SESSION_NOT_FOUND_11;
}
DeviceFiles handle;
if (!release_message) {
LOGE("CdmEngine::LoadUsageSession: no release message destination");
return INVALID_PARAMETERS_ENG_11;
}
DeviceFiles handle(file_system_);
if (!handle.Init(iter->second->GetSecurityLevel())) {
LOGE("CdmEngine::LoadUsageSession: unable to initialize device files");
return LOAD_USAGE_INFO_FILE_ERROR;
@@ -978,8 +1064,9 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
return status;
}
std::string server_url;
status = iter->second->GenerateReleaseRequest(release_message, &server_url);
CdmKeyRequest request;
status = iter->second->GenerateReleaseRequest(&request);
*release_message = request.message;
switch (status) {
case KEY_MESSAGE:
@@ -1021,24 +1108,86 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
// else we must be level 1 direct and we don't need to return a buffer.
}
CdmSessionMap::iterator iter;
CdmSessionMap::iterator session_iter = sessions_.end();
if (session_id.empty()) {
// Loop through the sessions to find the session containing the key_id.
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
// Loop through the sessions to find the session containing the key_id
// with the longest remaining license validity.
int64_t seconds_remaining = 0;
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
if (iter->second->IsKeyLoaded(*parameters.key_id)) {
break;
int64_t duration = iter->second->GetDurationRemaining();
if (duration > seconds_remaining) {
session_iter = iter;
seconds_remaining = duration;
}
}
}
} else {
iter = sessions_.find(session_id);
session_iter = sessions_.find(session_id);
}
if (iter == sessions_.end()) {
if (session_iter == sessions_.end()) {
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
session_id.c_str(), session_id.size());
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
return iter->second->Decrypt(parameters);
return session_iter->second->Decrypt(parameters);
}
CdmResponseType CdmEngine::GenericEncrypt(
const std::string& session_id, const std::string& in_buffer,
const std::string& key_id, const std::string& iv,
CdmEncryptionAlgorithm algorithm, std::string* out_buffer) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenericEncrypt: session_id not found = %s ",
session_id.c_str());
return SESSION_NOT_FOUND_13;
}
return iter->second->GenericEncrypt(in_buffer, key_id, iv, algorithm,
out_buffer);
}
CdmResponseType CdmEngine::GenericDecrypt(
const std::string& session_id, const std::string& in_buffer,
const std::string& key_id, const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenericDecrypt: session_id not found = %s ",
session_id.c_str());
return SESSION_NOT_FOUND_14;
}
return iter->second->GenericDecrypt(in_buffer, key_id, iv, algorithm,
out_buffer);
}
CdmResponseType CdmEngine::GenericSign(
const std::string& session_id, const std::string& message,
const std::string& key_id, CdmSigningAlgorithm algorithm,
std::string* signature) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenericSign: session_id not found = %s ",
session_id.c_str());
return SESSION_NOT_FOUND_15;
}
return iter->second->GenericSign(message, key_id, algorithm, signature);
}
CdmResponseType CdmEngine::GenericVerify(
const std::string& session_id, const std::string& message,
const std::string& key_id, CdmSigningAlgorithm algorithm,
const std::string& signature) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenericVerify: session_id not found = %s ",
session_id.c_str());
return SESSION_NOT_FOUND_16;
}
return iter->second->GenericVerify(message, key_id, algorithm, signature);
}
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
@@ -1058,25 +1207,30 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id,
return false;
}
CdmSessionMap::iterator iter = sessions_.find(*session_id);
if (iter != sessions_.end()) {
if (iter->second->IsKeyLoaded(key_id)) {
return true;
}
}
uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id);
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
CdmSessionMap::iterator session_iter = sessions_.end();
int64_t seconds_remaining = 0;
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
CdmSessionId local_session_id = iter->second->session_id();
if (Properties::GetSessionSharingId(local_session_id) ==
session_sharing_id) {
if (iter->second->IsKeyLoaded(key_id)) {
*session_id = local_session_id;
return true;
int64_t duration = iter->second->GetDurationRemaining();
if (duration > seconds_remaining) {
session_iter = iter;
seconds_remaining = duration;
}
}
}
}
if (session_iter != sessions_.end()) {
*session_id = session_iter->second->session_id();
return true;
}
return false;
}
@@ -1172,9 +1326,8 @@ void CdmEngine::DeleteAllUsageReportsUponFactoryReset() {
Properties::GetDeviceFilesBasePath(kSecurityLevelL3,
&device_base_path_level3);
File file;
if (!file.Exists(device_base_path_level1) &&
!file.Exists(device_base_path_level3)) {
if (!file_system_->Exists(device_base_path_level1) &&
!file_system_->Exists(device_base_path_level3)) {
scoped_ptr<CryptoSession> crypto_session(new CryptoSession());
CdmResponseType status = crypto_session->Open(
cert_provisioning_requested_security_level_);

View File

@@ -10,8 +10,6 @@
#include "cdm_engine.h"
#include "clock.h"
#include "crypto_session.h"
#include "device_files.h"
#include "file_store.h"
#include "log.h"
#include "properties.h"
@@ -25,47 +23,22 @@ const size_t kKeySetIdLength = 14;
namespace wvcdm {
CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
const std::string& origin,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id)
: initialized_(false),
session_id_(GenerateSessionId()),
origin_(origin),
crypto_session_(new CryptoSession),
file_handle_(new DeviceFiles),
license_received_(false),
is_offline_(false),
is_release_(false),
is_temporary_(false),
security_level_(kSecurityLevelUninitialized),
requested_security_level_(kLevelDefault),
is_initial_decryption_(true),
has_decrypted_since_last_report_(false),
is_initial_usage_update_(true),
is_usage_update_needed_(false) {
if (Properties::AlwaysUseKeySetIds()) {
if (forced_session_id) {
key_set_id_ = *forced_session_id;
} else {
bool ok = GenerateKeySetId(&key_set_id_);
(void)ok; // ok is now used when assertions are turned off.
assert(ok);
}
session_id_ = key_set_id_;
}
license_parser_.reset(new CdmLicense(session_id_));
policy_engine_.reset(new PolicyEngine(
session_id_, event_listener, crypto_session_.get()));
if (cdm_client_property_set) {
if (cdm_client_property_set->security_level() ==
QUERY_VALUE_SECURITY_LEVEL_L3) {
requested_security_level_ = kLevel3;
security_level_ = kSecurityLevelL3;
}
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
}
}
CdmSession::CdmSession(FileSystem* file_system) :
initialized_(false),
crypto_session_(new CryptoSession),
file_handle_(new DeviceFiles(file_system)),
license_received_(false),
is_offline_(false),
is_release_(false),
is_temporary_(false),
security_level_(kSecurityLevelUninitialized),
requested_security_level_(kLevelDefault),
is_initial_decryption_(true),
has_decrypted_since_last_report_(false),
is_initial_usage_update_(true),
is_usage_update_needed_(false),
mock_license_parser_in_use_(false),
mock_policy_engine_in_use_(false) {}
CdmSession::~CdmSession() {
if (!key_set_id_.empty()) {
@@ -75,24 +48,38 @@ CdmSession::~CdmSession() {
Properties::RemoveSessionPropertySet(session_id_);
}
CdmResponseType CdmSession::Init() {
if (session_id_.empty()) {
LOGE("CdmSession::Init: Failed, session not properly constructed");
return SESSION_INIT_ERROR_1;
}
CdmResponseType CdmSession::Init(
CdmClientPropertySet* cdm_client_property_set) {
return Init(cdm_client_property_set, NULL, NULL);
}
CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set,
const CdmSessionId* forced_session_id,
WvCdmEventListener* event_listener) {
if (initialized_) {
LOGE("CdmSession::Init: Failed due to previous initialization");
return SESSION_INIT_ERROR_2;
}
if (cdm_client_property_set &&
cdm_client_property_set->security_level() ==
QUERY_VALUE_SECURITY_LEVEL_L3) {
requested_security_level_ = kLevel3;
security_level_ = kSecurityLevelL3;
}
CdmResponseType sts = crypto_session_->Open(requested_security_level_);
if (NO_ERROR != sts) return sts;
security_level_ = crypto_session_->GetSecurityLevel();
if (!file_handle_->Init(security_level_)) {
LOGE("CdmSession::Init: Unable to initialize file handle");
return SESSION_FILE_HANDLE_INIT_ERROR;
}
std::string token;
if (Properties::use_certificates_as_identification()) {
std::string wrapped_key;
if (!file_handle_->Init(security_level_) ||
!file_handle_->RetrieveCertificate(origin_, &token, &wrapped_key) ||
if (!file_handle_->RetrieveCertificate(&token, &wrapped_key) ||
!crypto_session_->LoadCertificatePrivateKey(wrapped_key)) {
return NEED_PROVISIONING;
}
@@ -101,6 +88,30 @@ CdmResponseType CdmSession::Init() {
return SESSION_INIT_GET_KEYBOX_ERROR;
}
if (forced_session_id) {
key_set_id_ = *forced_session_id;
} else {
bool ok = GenerateKeySetId(&key_set_id_);
(void)ok; // ok is now used when assertions are turned off.
assert(ok);
}
session_id_ =
Properties::AlwaysUseKeySetIds() ? key_set_id_ : GenerateSessionId();
if (session_id_.empty()) {
LOGE("CdmSession::Init: empty session ID");
return EMPTY_SESSION_ID;
}
if (cdm_client_property_set)
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
if (!mock_license_parser_in_use_)
license_parser_.reset(new CdmLicense(session_id_));
if (!mock_policy_engine_in_use_)
policy_engine_.reset(new PolicyEngine(
session_id_, event_listener, crypto_session_.get()));
if (!license_parser_->Init(token, crypto_session_.get(),
policy_engine_.get()))
return LICENSE_PARSER_INIT_ERROR;
@@ -115,10 +126,6 @@ CdmResponseType CdmSession::RestoreOfflineSession(
const CdmKeySetId& key_set_id, const CdmLicenseType license_type) {
key_set_id_ = key_set_id;
// Retrieve license information from persistent store
if (!file_handle_->Reset(security_level_))
return RESTORE_OFFLINE_LICENSE_ERROR_1;
DeviceFiles::LicenseState license_state;
int64_t playback_start_time;
int64_t last_playback_time;
@@ -177,9 +184,9 @@ CdmResponseType CdmSession::RestoreUsageSession(
CdmResponseType CdmSession::GenerateKeyRequest(
const InitializationData& init_data, CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id) {
const CdmAppParameterMap& app_parameters,
CdmKeyRequest* key_request) {
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
return INVALID_CRYPTO_SESSION_1;
@@ -190,6 +197,11 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return CRYPTO_SESSION_OPEN_ERROR_1;
}
if (!key_request) {
LOGE("CdmSession::GenerateKeyRequest: No output destination provided");
return INVALID_PARAMETERS_ENG_5;
}
switch (license_type) {
case kLicenseTypeTemporary:
is_temporary_ = true;
@@ -230,13 +242,12 @@ CdmResponseType CdmSession::GenerateKeyRequest(
}
if (is_release_) {
if (key_request_type) *key_request_type = kKeyRequestTypeRelease;
return GenerateReleaseRequest(key_request, server_url);
return GenerateReleaseRequest(key_request);
} else if (license_received_) { // renewal
if (key_request_type) *key_request_type = kKeyRequestTypeRenewal;
return GenerateRenewalRequest(key_request, server_url);
return GenerateRenewalRequest(key_request);
} else {
if (key_request_type) *key_request_type = kKeyRequestTypeInitial;
key_request->type = kKeyRequestTypeInitial;
if (!license_parser_->HasInitData()) {
if (!init_data.is_supported()) {
LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)",
@@ -248,8 +259,7 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return INIT_DATA_NOT_FOUND;
}
}
if (is_offline_ && key_set_id_.empty() &&
!GenerateKeySetId(&key_set_id_)) {
if (is_offline_ && key_set_id_.empty()) {
LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID");
return KEY_REQUEST_ERROR_1;
}
@@ -257,24 +267,24 @@ CdmResponseType CdmSession::GenerateKeyRequest(
app_parameters_ = app_parameters;
CdmResponseType status = license_parser_->PrepareKeyRequest(
init_data, license_type,
app_parameters, key_request, server_url);
app_parameters, &key_request->message,
&key_request->url);
if (KEY_MESSAGE != status) return status;
key_request_ = *key_request;
key_request_ = key_request->message;
if (is_offline_) {
offline_init_data_ = init_data.data();
offline_release_server_url_ = *server_url;
offline_release_server_url_ = key_request->url;
}
if (key_set_id) *key_set_id = key_set_id_;
return KEY_MESSAGE;
}
}
// AddKey() - Accept license response and extract key info.
CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
CdmKeySetId* key_set_id) {
CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::AddKey: Invalid crypto session");
return INVALID_CRYPTO_SESSION_2;
@@ -303,12 +313,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
if (sts != NO_ERROR) return sts;
}
if (key_set_id) *key_set_id = key_set_id_;
return KEY_ADDED;
}
}
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) {
if (crypto_session_.get() == NULL) {
LOGE("CdmSession::QueryStatus: Invalid crypto session");
return INVALID_CRYPTO_SESSION_3;
@@ -321,17 +330,20 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
switch (security_level_) {
case kSecurityLevelL1:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L1;
break;
case kSecurityLevelL2:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2;
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L2;
break;
case kSecurityLevelL3:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_L3;
break;
case kSecurityLevelUninitialized:
case kSecurityLevelUnknown:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] =
(*query_response)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
@@ -340,24 +352,30 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
return NO_ERROR;
}
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
return policy_engine_->Query(key_info);
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* query_response) {
return policy_engine_->Query(query_response);
}
CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
CdmResponseType CdmSession::QueryKeyAllowedUsage(
const std::string& key_id, CdmKeyAllowedUsage* key_usage) {
return policy_engine_->QueryKeyAllowedUsage(key_id, key_usage);
}
CdmResponseType CdmSession::QueryOemCryptoSessionId(
CdmQueryMap* query_response) {
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session");
LOGW("CdmSession::QueryOemCryptoSessionId: Invalid crypto session");
return INVALID_CRYPTO_SESSION_4;
}
if (!crypto_session_->IsOpen()) {
LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open");
LOGW("CdmSession::QueryOemCryptoSessionId: Crypto session not open");
return CRYPTO_SESSION_OPEN_ERROR_4;
}
std::stringstream ss;
ss << crypto_session_->oec_session_id();
(*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
(*query_response)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
return NO_ERROR;
}
@@ -405,15 +423,17 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
// License renewal
// GenerateRenewalRequest() - Construct valid renewal request for the current
// session keys.
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
std::string* server_url) {
CdmResponseType CdmSession::GenerateRenewalRequest(
CdmKeyRequest* key_request) {
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
true, app_parameters_, key_request, server_url);
true, app_parameters_, &key_request->message, &key_request->url);
key_request->type = kKeyRequestTypeRenewal;
if (KEY_MESSAGE != status) return status;
if (is_offline_) {
offline_key_renewal_request_ = *key_request;
offline_key_renewal_request_ = key_request->message;
}
return KEY_MESSAGE;
}
@@ -432,11 +452,14 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
return KEY_ADDED;
}
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
std::string* server_url) {
CdmResponseType CdmSession::GenerateReleaseRequest(
CdmKeyRequest* key_request) {
is_release_ = true;
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
false, app_parameters_, key_request, server_url);
false, app_parameters_, &key_request->message,
&key_request->url);
key_request->type = kKeyRequestTypeRelease;
if (KEY_MESSAGE != status) return status;
@@ -463,6 +486,11 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
return license_parser_->IsKeyLoaded(key_id);
}
int64_t CdmSession::GetDurationRemaining() {
if (policy_engine_->IsLicenseForFuture()) return 0;
return policy_engine_->GetLicenseOrPlaybackDurationRemaining();
}
CdmSessionId CdmSession::GenerateSessionId() {
static int session_num = 1;
return SESSION_ID_PREFIX + IntToString(++session_num);
@@ -477,8 +505,6 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
std::vector<uint8_t> random_data(
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0);
if (!file_handle_->Reset(security_level_)) return false;
while (key_set_id->empty()) {
if (!crypto_session_->GetRandom(random_data.size(), &random_data[0]))
return false;
@@ -514,13 +540,6 @@ CdmResponseType CdmSession::StoreLicense() {
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::StoreLicense: Unable to store license");
CdmResponseType sts = Init();
if (sts != NO_ERROR) {
LOGW("CdmSession::StoreLicense: Reinitialization failed");
return sts;
}
key_set_id_.clear();
return STORE_LICENSE_ERROR_1;
}
return NO_ERROR;
@@ -533,11 +552,6 @@ CdmResponseType CdmSession::StoreLicense() {
return STORE_LICENSE_ERROR_2;
}
if (!file_handle_->Reset(security_level_)) {
LOGE("CdmSession::StoreLicense: Unable to initialize device files");
return STORE_LICENSE_ERROR_3;
}
std::string app_id;
GetApplicationId(&app_id);
if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_,
@@ -549,8 +563,6 @@ CdmResponseType CdmSession::StoreLicense() {
}
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
if (!file_handle_->Reset(security_level_)) return false;
return file_handle_->StoreLicense(
key_set_id_, state, offline_init_data_, key_request_, key_response_,
offline_key_renewal_request_, offline_key_renewal_response_,
@@ -558,14 +570,15 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
policy_engine_->GetLastPlaybackTime(), app_parameters_);
}
CdmResponseType CdmSession::ReleaseCrypto() {
crypto_session_->Close();
return NO_ERROR;
}
bool CdmSession::DeleteLicense() {
if (!is_offline_ && license_parser_->provider_session_token().empty())
return false;
if (!file_handle_->Reset(security_level_)) {
LOGE("CdmSession::DeleteLicense: Unable to initialize device files");
return false;
}
if (is_offline_) {
return file_handle_->DeleteLicense(key_set_id_);
} else {
@@ -613,13 +626,55 @@ CdmResponseType CdmSession::UpdateUsageInformation() {
return crypto_session_->UpdateUsageInformation();
}
CdmResponseType CdmSession::ReleaseCrypto() {
crypto_session_->Close();
return NO_ERROR;
CdmResponseType CdmSession::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (!out_buffer) {
LOGE("CdmSession::GenericEncrypt: No output destination provided");
return INVALID_PARAMETERS_ENG_6;
}
return crypto_session_->GenericEncrypt(in_buffer, key_id, iv, algorithm,
out_buffer);
}
CdmResponseType CdmSession::GenericDecrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
if (!out_buffer) {
LOGE("CdmSession::GenericDecrypt: No output destination provided");
return INVALID_PARAMETERS_ENG_7;
}
return crypto_session_->GenericDecrypt(in_buffer, key_id, iv, algorithm,
out_buffer);
}
CdmResponseType CdmSession::GenericSign(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
if (!signature) {
LOGE("CdmSession::GenericSign: No output destination provided");
return INVALID_PARAMETERS_ENG_8;
}
return crypto_session_->GenericSign(message, key_id, algorithm, signature);
}
CdmResponseType CdmSession::GenericVerify(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
return crypto_session_->GenericVerify(message, key_id, algorithm, signature);
}
// For testing only - takes ownership of pointers
void CdmSession::set_license_parser(CdmLicense* license_parser) {
license_parser_.reset(license_parser);
mock_license_parser_in_use_ = true;
}
void CdmSession::set_crypto_session(CryptoSession* crypto_session) {
@@ -628,6 +683,7 @@ void CdmSession::set_crypto_session(CryptoSession* crypto_session) {
void CdmSession::set_policy_engine(PolicyEngine* policy_engine) {
policy_engine_.reset(policy_engine);
mock_policy_engine_in_use_ = true;
}
void CdmSession::set_file_handle(DeviceFiles* file_handle) {

View File

@@ -189,7 +189,7 @@ bool CertificateProvisioning::ParseJsonResponse(
* Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
const std::string& origin, const CdmProvisioningResponse& response,
FileSystem* file_system, const CdmProvisioningResponse& response,
std::string* cert, std::string* wrapped_key) {
// Extracts signed response from JSON string, decodes base64 signed response
const std::string kMessageStart = "\"signedResponse\": \"";
@@ -259,12 +259,12 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
const std::string& device_certificate =
provisioning_response.device_certificate();
DeviceFiles handle;
DeviceFiles handle(file_system);
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
return CERT_PROVISIONING_RESPONSE_ERROR_7;
}
if (!handle.StoreCertificate(origin, device_certificate, wrapped_rsa_key)) {
if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) {
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
return CERT_PROVISIONING_RESPONSE_ERROR_8;
}

View File

@@ -40,7 +40,8 @@ CryptoSession::CryptoSession()
update_usage_table_after_close_session_(false),
is_destination_buffer_type_valid_(false),
requested_security_level_(kLevelDefault),
request_id_base_(0) {
request_id_base_(0),
cipher_mode_(kCipherModeCtr) {
Init();
}
@@ -55,17 +56,19 @@ void CryptoSession::Init() {
LOGV("CryptoSession::Init");
AutoLock auto_lock(crypto_lock_);
session_count_ += 1;
if (initialized_) return;
OEMCryptoResult sts = OEMCrypto_Initialize();
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Initialize failed: %d", sts);
return;
if (!initialized_) {
OEMCryptoResult sts = OEMCrypto_Initialize();
if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Initialize failed: %d", sts);
return;
}
initialized_ = true;
}
initialized_ = true;
}
void CryptoSession::Terminate() {
LOGV("CryptoSession::Terminate");
LOGE("CryptoSession::Terminate: initialized_=%d, session_count_=%d",
initialized_, session_count_);
AutoLock auto_lock(crypto_lock_);
if (session_count_ > 0) {
session_count_ -= 1;
@@ -233,6 +236,10 @@ bool CryptoSession::GetProvisioningId(std::string* provisioning_id) {
return true;
}
uint8_t CryptoSession::GetSecurityPatchLevel() {
return OEMCrypto_Security_Patch_Level(requested_security_level_);
}
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
LOGV("CryptoSession::Open: Lock");
AutoLock auto_lock(crypto_lock_);
@@ -247,16 +254,15 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_);
open_ = true;
} else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts,
session_count_, (int)initialized_);
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return INSUFFICIENT_CRYPTO_RESOURCES;
}
if (!open_) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts,
session_count_, (int)initialized_);
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return UNKNOWN_ERROR;
}
OEMCrypto_GetRandom(reinterpret_cast<uint8_t*>(&request_id_base_),
sizeof(request_id_base_));
++request_id_index_;
@@ -412,6 +418,10 @@ CdmResponseType CryptoSession::LoadKeys(
ko->key_control_iv = NULL;
ko->key_control = NULL;
}
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
? OEMCrypto_CipherMode_CBC
: OEMCrypto_CipherMode_CTR;
cipher_mode_ = ki->cipher_mode();
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
@@ -498,12 +508,22 @@ bool CryptoSession::RefreshKeys(const std::string& message,
}
bool CryptoSession::SelectKey(const std::string& key_id) {
// Crypto session lock already locked.
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
// Already using the desired key.
return true;
}
cached_key_id_ = key_id;
const uint8_t* key_id_string =
reinterpret_cast<const uint8_t*>(key_id.data());
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
OEMCryptoResult sts =
OEMCrypto_SelectKey(oec_session_id_, key_id_string, key_id.size());
OEMCrypto_SelectKey(oec_session_id_, key_id_string,
cached_key_id_.size());
if (OEMCrypto_SUCCESS != sts) {
cached_key_id_.clear();
return false;
}
return true;
@@ -558,78 +578,76 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message,
bool CryptoSession::GenerateSignature(const std::string& message,
std::string* signature) {
LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_);
if (!signature) return false;
if (!signature) {
LOGE("GenerateSignature: null signature string");
return false;
}
OEMCryptoResult sts;
size_t length = signature->size();
OEMCryptoResult sts = OEMCrypto_GenerateSignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length);
if (OEMCrypto_SUCCESS != sts) {
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
}
// Retry with proper-sized signature buffer
signature->resize(length);
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
sts = OEMCrypto_GenerateSignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length);
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return true;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized signature buffer
signature->resize(length);
}
// Trim signature buffer
signature->resize(length);
return true;
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
}
bool CryptoSession::GenerateRsaSignature(const std::string& message,
std::string* signature) {
LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_);
if (!signature) return false;
if (!signature) {
LOGE("GenerateRsaSignature: null signature string");
return false;
}
OEMCryptoResult sts;
signature->resize(kRsaSignatureLength);
size_t length = signature->size();
OEMCryptoResult sts = OEMCrypto_GenerateRSASignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())), &length,
kSign_RSASSA_PSS);
if (OEMCrypto_SUCCESS != sts) {
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
return false;
}
// Retry with proper-sized signature buffer
signature->resize(length);
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
sts = OEMCrypto_GenerateRSASignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length, kSign_RSASSA_PSS);
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
return false;
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return true;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized signature buffer
signature->resize(length);
}
// Trim signature buffer
signature->resize(length);
return true;
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
return false;
}
CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
@@ -666,27 +684,32 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
}
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (!params.is_encrypted) {
if (!params.is_encrypted &&
params.subsample_flags ==
(OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)) {
sts = OEMCrypto_CopyBuffer(requested_security_level_,
params.encrypt_buffer, params.encrypt_length,
&buffer_descriptor, params.subsample_flags);
}
if (params.is_encrypted && params.cipher_mode != cipher_mode_) {
return INCORRECT_CRYPTO_MODE;
}
if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
OEMCrypto_CENCEncryptPatternDesc pattern_descriptor;
pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks;
pattern_descriptor.skip = params.pattern_descriptor.skip_blocks;
pattern_descriptor.offset = params.pattern_descriptor.offset_blocks;
AutoLock auto_lock(crypto_lock_);
// Check if key needs to be selected
if (params.is_encrypted) {
if (key_id_ != *params.key_id) {
if (SelectKey(*params.key_id)) {
key_id_ = *params.key_id;
} else {
return NEED_KEY;
}
if (!SelectKey(*params.key_id)) {
return NEED_KEY;
}
}
sts = OEMCrypto_DecryptCTR(
sts = OEMCrypto_DecryptCENC(
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
params.is_encrypted, &(*params.iv).front(), params.block_offset,
&buffer_descriptor, params.subsample_flags);
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
}
switch (sts) {
@@ -696,6 +719,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
return INSUFFICIENT_CRYPTO_RESOURCES;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INVALID_SESSION:
return SESSION_NOT_FOUND_FOR_DECRYPT;
case OEMCrypto_ERROR_DECRYPT_FAILED:
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return DECRYPT_ERROR;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
default:
return UNKNOWN_ERROR;
}
@@ -1076,4 +1106,200 @@ bool CryptoSession::GetMaxNumberOfSessions(size_t* max) {
return true;
}
CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("GenericEncrypt: id=%ld", (uint32_t)oec_session_id_);
if (!out_buffer) return INVALID_PARAMETERS_ENG_9;
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_13;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
return KEY_ERROR_1;
}
OEMCryptoResult sts = OEMCrypto_Generic_Encrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data())));
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenericEncrypt: OEMCrypto_Generic_Encrypt err=%d", sts);
if (OEMCrypto_ERROR_KEY_EXPIRED == sts ||
OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
return KEY_NOT_FOUND_3;
} else {
return UNKNOWN_ERROR;
}
}
return NO_ERROR;
}
CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
const std::string& key_id,
const std::string& iv,
CdmEncryptionAlgorithm algorithm,
std::string* out_buffer) {
LOGV("GenericDecrypt: id=%ld", (uint32_t)oec_session_id_);
if (!out_buffer) return INVALID_PARAMETERS_ENG_10;
OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm);
if (iv.size() != GenericEncryptionBlockSize(algorithm) ||
oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_14;
}
if (out_buffer->size() < in_buffer.size()) {
out_buffer->resize(in_buffer.size());
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
return KEY_ERROR_2;
}
OEMCryptoResult sts = OEMCrypto_Generic_Decrypt(
oec_session_id_, reinterpret_cast<const uint8_t*>(in_buffer.data()),
in_buffer.size(), reinterpret_cast<const uint8_t*>(iv.data()),
oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(out_buffer->data())));
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenericDecrypt: OEMCrypto_Generic_Decrypt err=%d", sts);
if (OEMCrypto_ERROR_KEY_EXPIRED == sts ||
OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
return KEY_NOT_FOUND_4;
} else {
return UNKNOWN_ERROR;
}
}
return NO_ERROR;
}
CdmResponseType CryptoSession::GenericSign(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
std::string* signature) {
LOGV("GenericSign: id=%ld", (uint32_t)oec_session_id_);
if (!signature) {
LOGE("GenerateSign: null signature string");
return INVALID_PARAMETERS_ENG_11;
}
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
if (oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_15;
}
OEMCryptoResult sts;
size_t length = signature->size();
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
return KEY_ERROR_3;
}
// At most two attempts.
// The first attempt may fail due to buffer too short
for (int i = 0; i < 2; ++i) {
sts = OEMCrypto_Generic_Sign(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
&length);
if (OEMCrypto_SUCCESS == sts) {
// Trim signature buffer and done
signature->resize(length);
return NO_ERROR;
}
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
break;
}
// Retry with proper-sized return buffer
signature->resize(length);
}
LOGE("GenericSign: OEMCrypto_Generic_Sign err=%d", sts);
if (OEMCrypto_ERROR_KEY_EXPIRED == sts ||
OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
return KEY_NOT_FOUND_5;
} else {
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenericVerify(const std::string& message,
const std::string& key_id,
CdmSigningAlgorithm algorithm,
const std::string& signature) {
LOGV("GenericVerify: id=%ld", (uint32_t)oec_session_id_);
OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm);
if (oec_algorithm == kInvalidAlgorithm) {
return INVALID_PARAMETERS_ENG_16;
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
return KEY_ERROR_4;
}
OEMCryptoResult sts = OEMCrypto_Generic_Verify(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), oec_algorithm,
reinterpret_cast<const uint8_t*>(signature.data()), signature.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenericVerify: OEMCrypto_Generic_Verify err=%d", sts);
if (OEMCrypto_ERROR_KEY_EXPIRED == sts ||
OEMCrypto_ERROR_NO_CONTENT_KEY == sts) {
return KEY_NOT_FOUND_6;
} else {
return UNKNOWN_ERROR;
}
}
return NO_ERROR;
}
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
CdmSigningAlgorithm algorithm) {
if (kSigningAlgorithmHmacSha256 == algorithm) {
return OEMCrypto_HMAC_SHA256;
} else {
return kInvalidAlgorithm;
}
}
OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm(
CdmEncryptionAlgorithm algorithm) {
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
return OEMCrypto_AES_CBC_128_NO_PADDING;
} else {
return kInvalidAlgorithm;
}
}
size_t CryptoSession::GenericEncryptionBlockSize(
CdmEncryptionAlgorithm algorithm) {
if (kEncryptionAlgorithmAesCbc128 == algorithm) {
return kAes128BlockSize;
} else {
return 0;
}
}
} // namespace wvcdm

View File

@@ -18,13 +18,16 @@
#define MD5 CC_MD5
#define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH
#else
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#endif
// Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::HlsAttributes;
using video_widevine_client::sdk::HlsAttributes_Method_AES_128;
using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES;
using video_widevine_client::sdk::License;
using video_widevine_client::sdk::License_LicenseState_ACTIVE;
using video_widevine_client::sdk::License_LicenseState_RELEASING;
@@ -34,19 +37,13 @@ using video_widevine_client::sdk::UsageInfo_ProviderSession;
namespace {
const char kCertificateFileNamePrefix[] = "cert";
const char kCertificateFileNameExt[] = ".bin";
const char kCertificateFileName[] = "cert.bin";
const char kHlsAttributesFileNameExt[] = ".hal";
const char kUsageInfoFileNamePrefix[] = "usage";
const char kUsageInfoFileNameExt[] = ".bin";
const char kLicenseFileNameExt[] = ".lic";
const char kEmptyFileName[] = "";
const char kWildcard[] = "*";
const char kDirectoryDelimiter = '/';
const char* kSecurityLevelPathCompatibilityExclusionList[] = {
"ay64.dat", "ay64.dat2", "ay64.dat3"};
size_t kSecurityLevelPathCompatibilityExclusionListSize =
sizeof(kSecurityLevelPathCompatibilityExclusionList) /
sizeof(*kSecurityLevelPathCompatibilityExclusionList);
bool Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
@@ -66,30 +63,30 @@ namespace wvcdm {
// static
std::set<std::string> DeviceFiles::reserved_license_ids_;
DeviceFiles::DeviceFiles()
: file_(NULL),
DeviceFiles::DeviceFiles(FileSystem* file_system)
: file_system_(file_system),
security_level_(kSecurityLevelUninitialized),
initialized_(false),
test_file_(false) {}
initialized_(false) {}
DeviceFiles::~DeviceFiles() {
if (test_file_) file_.release();
}
DeviceFiles::~DeviceFiles() {}
bool DeviceFiles::Init(CdmSecurityLevel security_level) {
if (!file_system_) {
LOGD("DeviceFiles::Init: Invalid FileSystem given.");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level, &path)) {
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
return false;
}
if (!test_file_) file_.reset(new File());
security_level_ = security_level;
initialized_ = true;
return true;
}
bool DeviceFiles::StoreCertificate(const std::string& origin,
const std::string& certificate,
bool DeviceFiles::StoreCertificate(const std::string& certificate,
const std::string& wrapped_private_key) {
if (!initialized_) {
LOGW("DeviceFiles::StoreCertificate: not initialized");
@@ -109,23 +106,18 @@ bool DeviceFiles::StoreCertificate(const std::string& origin,
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(GetCertificateFileName(origin), serialized_file);
return StoreFileWithHash(GetCertificateFileName(), serialized_file);
}
bool DeviceFiles::RetrieveCertificate(const std::string& origin,
std::string* certificate,
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
std::string* wrapped_private_key) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveCertificate: not initialized");
return false;
}
if (Properties::security_level_path_backward_compatibility_support()) {
SecurityLevelPathBackwardCompatibility();
}
video_widevine_client::sdk::File file;
if (!RetrieveHashedFile(GetCertificateFileName(origin), &file)) {
if (!RetrieveHashedFile(GetCertificateFileName(), &file)) {
return false;
}
@@ -151,22 +143,22 @@ bool DeviceFiles::RetrieveCertificate(const std::string& origin,
return true;
}
bool DeviceFiles::HasCertificate(const std::string& origin) {
bool DeviceFiles::HasCertificate() {
if (!initialized_) {
LOGW("DeviceFiles::HasCertificate: not initialized");
return false;
}
return FileExists(GetCertificateFileName(origin));
return FileExists(GetCertificateFileName());
}
bool DeviceFiles::RemoveCertificate(const std::string& origin) {
bool DeviceFiles::RemoveCertificate() {
if (!initialized_) {
LOGW("DeviceFiles::RemoveCertificate: not initialized");
return false;
}
return RemoveFile(GetCertificateFileName(origin));
return RemoveFile(GetCertificateFileName());
}
bool DeviceFiles::StoreLicense(
@@ -534,13 +526,105 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId(
return false;
}
bool DeviceFiles::StoreFileWithHash(const std::string& name,
const std::string& serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle");
bool DeviceFiles::StoreHlsAttributes(
const std::string& key_set_id, const CdmHlsMethod method,
const std::vector<uint8_t>& media_segment_iv) {
if (!initialized_) {
LOGW("DeviceFiles::StoreHlsAttributes: not initialized");
return false;
}
// Fill in file information
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
HlsAttributes* hls_attributes = file.mutable_hls_attributes();
switch (method) {
case kHlsMethodAes128:
hls_attributes->set_method(HlsAttributes_Method_AES_128);
break;
case kHlsMethodSampleAes:
hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES);
break;
case kHlsMethodNone:
default:
LOGW("DeviceFiles::StoreHlsAttributeInfo: Unknown HLS method: %u",
method);
return false;
break;
}
hls_attributes->set_media_segment_iv(&media_segment_iv[0],
media_segment_iv.size());
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt,
serialized_file);
}
bool DeviceFiles::RetrieveHlsAttributes(
const std::string& key_set_id, CdmHlsMethod* method,
std::vector<uint8_t>* media_segment_iv) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveHlsAttributes: not initialized");
return false;
}
video_widevine_client::sdk::File file;
if (!RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file)) {
return false;
}
if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) {
LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file type: %u",
file.type());
return false;
}
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file version: %u",
file.version());
return false;
}
if (!file.has_hls_attributes()) {
LOGW("DeviceFiles::RetrieveHlsAttributes: HLS attributes not present");
return false;
}
HlsAttributes attributes = file.hls_attributes();
switch (attributes.method()) {
case HlsAttributes_Method_AES_128:
*method = kHlsMethodAes128;
break;
case HlsAttributes_Method_SAMPLE_AES:
*method = kHlsMethodSampleAes;
break;
default:
LOGW("DeviceFiles::RetrieveHlsAttributes: Unrecognized HLS method: %u",
attributes.method());
*method = kHlsMethodNone;
break;
}
media_segment_iv->assign(attributes.media_segment_iv().begin(),
attributes.media_segment_iv().end());
return true;
}
bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) {
if (!initialized_) {
LOGW("DeviceFiles::DeleteHlsAttributes: not initialized");
return false;
}
return RemoveFile(key_set_id + kHlsAttributesFileNameExt);
}
bool DeviceFiles::StoreFileWithHash(const std::string& name,
const std::string& serialized_file) {
// calculate SHA hash
std::string hash;
if (!Hash(serialized_file, &hash)) {
@@ -561,30 +645,23 @@ bool DeviceFiles::StoreFileWithHash(const std::string& name,
bool DeviceFiles::StoreFileRaw(const std::string& name,
const std::string& serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::StoreFileRaw: Invalid file handle");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::StoreFileRaw: Unable to get base path");
return false;
}
if (!file_->IsDirectory(path)) {
if (!file_->CreateDirectory(path)) return false;
}
path += name;
if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) {
File* file =
file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate);
if (!file) {
LOGW("DeviceFiles::StoreFileRaw: File open failed: %s", path.c_str());
return false;
}
ssize_t bytes = file_->Write(serialized_file.data(), serialized_file.size());
file_->Close();
ssize_t bytes = file->Write(serialized_file.data(), serialized_file.size());
file->Close();
if (bytes != static_cast<ssize_t>(serialized_file.size())) {
LOGW(
@@ -599,16 +676,12 @@ bool DeviceFiles::StoreFileRaw(const std::string& name,
return true;
}
bool DeviceFiles::RetrieveHashedFile(const std::string& name,
video_widevine_client::sdk::File* file) {
bool DeviceFiles::RetrieveHashedFile(
const std::string& name,
video_widevine_client::sdk::File* deserialized_file) {
std::string serialized_file;
if (!file_.get()) {
LOGW("DeviceFiles::RetrieveHashedFile: Invalid file handle");
return false;
}
if (!file) {
if (!deserialized_file) {
LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file parameter");
return false;
}
@@ -621,29 +694,30 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name,
path += name;
if (!file_->Exists(path)) {
if (!file_system_->Exists(path)) {
LOGW("DeviceFiles::RetrieveHashedFile: %s does not exist", path.c_str());
return false;
}
ssize_t bytes = file_->FileSize(path);
ssize_t bytes = file_system_->FileSize(path);
if (bytes <= 0) {
LOGW("DeviceFiles::RetrieveHashedFile: File size invalid: %s",
path.c_str());
// Remove the corrupted file so the caller will not get the same error
// when trying to access the file repeatedly, causing the system to stall.
file_->Remove(path);
file_system_->Remove(path);
return false;
}
if (!file_->Open(path, File::kReadOnly | File::kBinary)) {
File* file = file_system_->Open(path, FileSystem::kReadOnly);
if (!file) {
return false;
}
std::string serialized_hash_file;
serialized_hash_file.resize(bytes);
bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size());
file_->Close();
bytes = file->Read(&serialized_hash_file[0], serialized_hash_file.size());
file->Close();
if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
LOGW("DeviceFiles::RetrieveHashedFile: read failed");
@@ -669,11 +743,11 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name,
LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch");
// Remove the corrupted file so the caller will not get the same error
// when trying to access the file repeatedly, causing the system to stall.
file_->Remove(path);
file_system_->Remove(path);
return false;
}
if (!file->ParseFromString(hash_file.file())) {
if (!deserialized_file->ParseFromString(hash_file.file())) {
LOGW("DeviceFiles::RetrieveHashedFile: Unable to parse file");
return false;
}
@@ -681,11 +755,6 @@ bool DeviceFiles::RetrieveHashedFile(const std::string& name,
}
bool DeviceFiles::FileExists(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::FileExists: Invalid file handle");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::FileExists: Unable to get base path");
@@ -693,15 +762,10 @@ bool DeviceFiles::FileExists(const std::string& name) {
}
path += name;
return file_->Exists(path);
return file_system_->Exists(path);
}
bool DeviceFiles::RemoveFile(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::RemoveFile: Invalid file handle");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::RemoveFile: Unable to get base path");
@@ -709,15 +773,10 @@ bool DeviceFiles::RemoveFile(const std::string& name) {
}
path += name;
return file_->Remove(path);
return file_system_->Remove(path);
}
ssize_t DeviceFiles::GetFileSize(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::GetFileSize: Invalid file handle");
return -1;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::GetFileSize: Unable to get base path");
@@ -725,79 +784,15 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) {
}
path += name;
return file_->FileSize(path);
return file_system_->FileSize(path);
}
void DeviceFiles::SecurityLevelPathBackwardCompatibility() {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW(
"DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
"get base path");
return;
}
std::vector<std::string> security_dirs;
if (!Properties::GetSecurityLevelDirectories(&security_dirs)) {
LOGW(
"DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
"get security directories");
return;
}
size_t pos = std::string::npos;
for (size_t i = 0; i < security_dirs.size(); ++i) {
pos = path.find(security_dirs[i]);
if (pos != std::string::npos && pos > 0 &&
pos == path.size() - security_dirs[i].size() &&
path[pos - 1] == kDirectoryDelimiter) {
break;
}
}
if (pos == std::string::npos) {
LOGV(
"DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level "
"specific path not found. Check properties?");
return;
}
std::string from_dir(path, 0, pos);
std::vector<std::string> files;
if (!file_->List(from_dir, &files)) {
return;
}
for (size_t i = 0; i < files.size(); ++i) {
std::string from = from_dir + files[i];
bool exclude = false;
for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize;
++j) {
if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) {
exclude = true;
break;
}
}
if (exclude) continue;
if (!file_->IsRegularFile(from)) continue;
for (size_t j = 0; j < security_dirs.size(); ++j) {
std::string to_dir = from_dir + security_dirs[j];
if (!file_->Exists(to_dir)) file_->CreateDirectory(to_dir);
std::string to = to_dir + files[i];
file_->Copy(from, to);
}
file_->Remove(from);
}
std::string DeviceFiles::GetCertificateFileName() {
return kCertificateFileName;
}
std::string DeviceFiles::GetCertificateFileName(const std::string& origin) {
std::string hash;
if (origin != EMPTY_ORIGIN) {
hash = GetFileNameSafeHash(origin);
}
return kCertificateFileNamePrefix + hash + kCertificateFileNameExt;
std::string DeviceFiles::GetHlsAttributesFileNameExtension() {
return kHlsAttributesFileNameExt;
}
std::string DeviceFiles::GetLicenseFileNameExtension() {
@@ -820,9 +815,4 @@ std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) {
return wvcdm::Base64SafeEncode(hash);
}
void DeviceFiles::SetTestFile(File* file) {
file_.reset(file);
test_file_ = true;
}
} // namespace wvcdm

View File

@@ -52,11 +52,21 @@ message UsageInfo {
repeated ProviderSession sessions = 1;
}
message HlsAttributes {
enum Method {
AES_128 = 1;
SAMPLE_AES = 2;
}
optional Method method = 1;
optional bytes media_segment_iv = 2;
}
message File {
enum FileType {
DEVICE_CERTIFICATE = 1;
LICENSE = 2;
USAGE_INFO = 3;
HLS_ATTRIBUTES = 4;
}
enum FileVersion {
@@ -68,6 +78,7 @@ message File {
optional DeviceCertificate device_certificate = 3;
optional License license = 4;
optional UsageInfo usage_info = 5;
optional HlsAttributes hls_attributes = 6;
}
message HashedFile {

View File

@@ -2,31 +2,70 @@
#include "initialization_data.h"
#include <arpa/inet.h>
#include <string.h>
#include "buffer_reader.h"
#include "jsmn.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
namespace {
const char kKeyFormatVersionsSeparator = '/';
const char kColon = ':';
const char kDoubleQuote = '\"';
const char kLeftBracket = '[';
const char kRightBracket = ']';
const std::string kBase64String = "base64,";
const uint32_t kFourCcCbc1 = 0x63626331;
const uint32_t kFourCcCbcs = 0x63626373;
// json init data key values
const std::string kProvider = "provider";
const std::string kContentId = "content_id";
const std::string kKeyIds = "key_ids";
// Being conservative, usually we expect 6 + number of Key Ids
const int kDefaultNumJsonTokens = 128;
} // namespace
namespace wvcdm {
// Protobuf generated classes.
using video_widevine_server::sdk::WidevineCencHeader;
using video_widevine_server::sdk::WidevineCencHeader_Algorithm;
using video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR;
InitializationData::InitializationData(const std::string& type,
const CdmInitData& data)
: type_(type), is_cenc_(false), is_webm_(false) {
: type_(type),
is_cenc_(false),
is_hls_(false),
is_webm_(false),
hls_method_(kHlsMethodNone) {
if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE ||
type == CENC_INIT_DATA_FORMAT) {
is_cenc_ = true;
} else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE ||
type == WEBM_INIT_DATA_FORMAT) {
is_webm_ = true;
} else if (type == HLS_INIT_DATA_FORMAT) {
is_hls_ = true;
}
if (is_supported()) {
if (is_cenc()) {
ExtractWidevinePssh(data, &data_);
} else {
} else if (is_webm()) {
data_ = data;
} else if (is_hls()) {
std::string uri;
if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) {
ConstructWidevineInitData(hls_method_, uri, &data_);
}
}
}
}
@@ -61,19 +100,22 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// atom size, used for skipping.
uint64_t size;
if (!reader.Read4Into8(&size)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom size.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read atom size.");
return false;
}
std::vector<uint8_t> atom_type;
if (!reader.ReadVec(&atom_type, 4)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read atom type.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read atom type.");
return false;
}
if (size == 1) {
if (!reader.Read8(&size)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom "
"size.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read 64-bit "
"atom size.");
return false;
}
} else if (size == 0) {
@@ -82,10 +124,12 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// "pssh"
if (memcmp(&atom_type[0], "pssh", 4)) {
LOGV("CdmEngine::ExtractWidevinePssh: PSSH literal not present.");
LOGV(
"InitializationData::ExtractWidevinePssh: PSSH literal not present.");
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
"the atom.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
"of the atom.");
return false;
}
continue;
@@ -94,15 +138,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// version
uint8_t version;
if (!reader.Read1(&version)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read PSSH "
"version.");
return false;
}
if (version > 1) {
// unrecognized version - skip.
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
"the atom.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
"of the atom.");
return false;
}
continue;
@@ -110,25 +157,31 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// flags
if (!reader.SkipBytes(3)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to skip the PSSH "
"flags.");
return false;
}
// system id
std::vector<uint8_t> system_id;
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read system ID.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read system ID.");
return false;
}
if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) {
// skip non-Widevine PSSH boxes.
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of "
"the atom.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
"of the atom.");
return false;
}
LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH.");
LOGV(
"InitializationData::ExtractWidevinePssh: Skipping non-Widevine "
"PSSH.");
continue;
}
@@ -136,11 +189,14 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// v1 has additional fields for key IDs. We can skip them.
uint32_t num_key_ids;
if (!reader.Read4(&num_key_ids)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read num key "
"IDs.");
return false;
}
if (!reader.SkipBytes(num_key_ids * 16)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to skip key IDs.");
return false;
}
}
@@ -148,13 +204,16 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// size of PSSH data
uint32_t data_length;
if (!reader.Read4(&data_length)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data "
"size.");
return false;
}
output->clear();
if (!reader.ReadString(output, data_length)) {
LOGV("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data.");
LOGV(
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data.");
return false;
}
@@ -165,4 +224,357 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
return false;
}
// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it
// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out
// method, IV, URI and WV init data.
//
// An example of a widevine supported attribute list from an HLS media playlist
// is,
// "EXT-X-KEY: METHOD=SAMPLE-AES, \"
// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg"
// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo"
// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ"
// "o=, \"
// "IV=0x6df49213a781e338628d0e9c812d328e, \"
// "KEYFORMAT=”com.widevine”, \"
// "KEYFORMATVERSIONS=”1”"
bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
CdmHlsMethod* method,
std::vector<uint8_t>* iv,
std::string* uri) {
std::string value;
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE,
&value)) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
"keyformat value");
return false;
}
if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS "
"keyformat value: %s",
value.c_str());
return false;
}
// KEYFORMATVERSIONS is an optional parameter. If absent its
// value defaults to "1"
if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE,
&value)) {
std::vector<std::string> versions = ExtractKeyFormatVersions(value);
bool supported = false;
for (size_t i = 0; i < versions.size(); ++i) {
if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) {
supported = true;
break;
}
}
if (!supported) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat "
"version is not supported: %s",
value.c_str());
return false;
}
}
if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
"method");
return false;
}
if (value.compare(HLS_METHOD_AES_128) == 0) {
*method = kHlsMethodAes128;
} else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) {
*method = kHlsMethodSampleAes;
} else if (value.compare(HLS_METHOD_NONE) == 0) {
*method = kHlsMethodNone;
} else {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: HLS method "
"unrecognized: %s",
value.c_str());
return false;
}
if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute "
"not present");
return false;
}
if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) {
LOGV(
"InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute "
"not present");
return false;
}
return true;
}
// Extracts a base64 encoded string from URI data. This is then base64 decoded
// and the Json formatted init data is then parsed. The information is used
// to generate a Widevine init data protobuf (WidevineCencHeader).
//
// An example of a widevine supported json formatted init data string is,
//
// {
// "provider":"mlbamhbo",
// "content_id":"MjAxNV9UZWFycw==",
// "key_ids":
// [
// "371e135e1a985d75d198a7f41020dc23"
// ]
// }
bool InitializationData::ConstructWidevineInitData(
CdmHlsMethod method, const std::string& uri, CdmInitData* init_data_proto) {
if (!init_data_proto) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter");
return false;
}
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
return false;
}
size_t pos = uri.find(kBase64String);
if (pos == std::string::npos) {
LOGV(
"InitializationData::ConstructWidevineInitData: URI attribute "
"unexpected format: %s",
uri.c_str());
return false;
}
std::vector<uint8_t> json_init_data =
Base64Decode(uri.substr(pos + kBase64String.size()));
if (json_init_data.size() == 0) {
LOGV(
"InitializationData::ConstructWidevineInitData: Base64 decode of json "
"data failed");
return false;
}
std::string json_string((const char*)(&json_init_data[0]),
json_init_data.size());
// Parse the Json string using jsmn
jsmn_parser parser;
jsmntok_t tokens[kDefaultNumJsonTokens];
jsmn_init(&parser);
int num_of_tokens =
jsmn_parse(&parser, json_string.c_str(), json_string.size(), tokens,
kDefaultNumJsonTokens);
if (num_of_tokens <= 0) {
LOGV(
"InitializationData::ConstructWidevineInitData: Json parsing failed: "
"%d",
num_of_tokens);
return false;
}
std::string provider;
std::string content_id;
std::vector<std::string> key_ids;
enum JsmnParserState {
kParseState,
kProviderState,
kContentIdState,
kKeyIdsState,
} state = kParseState;
int number_of_key_ids = 0;
// Extract the provider, content_id and key_ids
for (int i = 0; i < num_of_tokens; ++i) {
if (tokens[i].start < 0 || tokens[i].end < 0) {
LOGV(
"InitializationData::ConstructWidevineInitData: Invalid start or end "
"of token");
return false;
}
switch (state) {
case kParseState:
if (tokens[i].type == JSMN_STRING) {
std::string token(json_string, tokens[i].start,
tokens[i].end - tokens[i].start);
if (token == kProvider) {
state = kProviderState;
} else if (token == kContentId) {
state = kContentIdState;
} else if (token == kKeyIds) {
state = kKeyIdsState;
}
}
break;
case kProviderState:
if (tokens[i].type == JSMN_STRING) {
provider.assign(json_string, tokens[i].start,
tokens[i].end - tokens[i].start);
}
state = kParseState;
break;
case kContentIdState:
if (tokens[i].type == JSMN_STRING) {
std::string base64_content_id(json_string, tokens[i].start,
tokens[i].end - tokens[i].start);
std::vector<uint8_t> content_id_data =
Base64Decode(base64_content_id);
content_id.assign(reinterpret_cast<const char*>(&content_id_data[0]),
content_id_data.size());
}
state = kParseState;
break;
case kKeyIdsState:
if (tokens[i].type == JSMN_ARRAY) {
number_of_key_ids = tokens[i].size;
} else if (tokens[i].type == JSMN_STRING) {
std::string key_id(a2bs_hex(json_string.substr(
tokens[i].start, tokens[i].end - tokens[i].start)));
if (key_id.size() == 16) key_ids.push_back(key_id);
--number_of_key_ids;
} else {
state = kParseState;
}
if (number_of_key_ids <= 0) state = kParseState;
break;
}
}
if (provider.size() == 0) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid provider");
return false;
}
if (content_id.size() == 0) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid content_id");
return false;
}
if (key_ids.size() == 0) {
LOGV("InitializationData::ConstructWidevineInitData: No key_ids present");
return false;
}
// Now format as Widevine init data protobuf
WidevineCencHeader cenc_header;
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
// have not yet been pushed to production. Set until then.
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
for (size_t i = 0; i < key_ids.size(); ++i) {
cenc_header.add_key_id(key_ids[i]);
}
cenc_header.set_provider(provider);
cenc_header.set_content_id(content_id);
if (method == kHlsMethodAes128)
cenc_header.set_protection_scheme(htonl(kFourCcCbc1));
else
cenc_header.set_protection_scheme(htonl(kFourCcCbcs));
cenc_header.SerializeToString(init_data_proto);
return true;
}
bool InitializationData::ExtractQuotedAttribute(
const std::string& attribute_list, const std::string& key,
std::string* value) {
bool result = ExtractAttribute(attribute_list, key, value);
if (value->size() < 2 || value->at(0) != '\"' ||
value->at(value->size() - 1) != '\"')
return false;
*value = value->substr(1, value->size() - 2);
if (value->find('\"') != std::string::npos) return false;
return result;
}
bool InitializationData::ExtractHexAttribute(const std::string& attribute_list,
const std::string& key,
std::vector<uint8_t>* value) {
std::string val;
bool result = ExtractAttribute(attribute_list, key, &val);
if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' ||
((val[1] != 'x') && (val[1] != 'X')))
return false;
for (size_t i = 2; i < val.size(); ++i) {
if (!isxdigit(val[i])) return false;
}
*value = a2b_hex(val.substr(2, val.size() - 2));
return result;
}
bool InitializationData::ExtractAttribute(const std::string& attribute_list,
const std::string& key,
std::string* value) {
// validate the key
for (size_t i = 0; i < key.size(); ++i)
if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false;
bool found = false;
size_t pos = 0;
// Find the key followed by '='
while (!found) {
pos = attribute_list.find(key, pos);
if (pos == std::string::npos) return false;
pos += key.size();
if (attribute_list[pos] != '=') continue;
found = true;
}
if (attribute_list.size() <= ++pos) return false;
size_t end_pos = pos;
found = false;
// Find the next comma outside the quote or end of string
while (!found) {
end_pos = attribute_list.find(',', end_pos);
if (end_pos != std::string::npos && attribute_list[pos] == '\"' &&
attribute_list[end_pos - 1] != '\"') {
++end_pos;
continue;
}
if (end_pos == std::string::npos)
end_pos = attribute_list.size() - 1;
else
--end_pos;
found = true;
}
*value = attribute_list.substr(pos, end_pos - pos + 1);
// validate the value
for (size_t i = 0; i < value->size(); ++i)
if (!isgraph(value->at(i))) return false;
return true;
}
// Key format versions are individual values or multiple versions
// separated by '/'. "1" or "1/2/5"
std::vector<std::string> InitializationData::ExtractKeyFormatVersions(
const std::string& key_format_versions) {
std::vector<std::string> versions;
size_t pos = 0;
while (pos < key_format_versions.size()) {
size_t next_pos =
key_format_versions.find(kKeyFormatVersionsSeparator, pos);
if (next_pos == std::string::npos) {
versions.push_back(key_format_versions.substr(pos));
break;
} else {
versions.push_back(key_format_versions.substr(pos, next_pos - pos));
pos = next_pos + 1;
}
}
return versions;
}
} // namespace wvcdm

View File

@@ -2,6 +2,8 @@
#include "license.h"
#include <arpa/inet.h>
#include <sstream>
#include <vector>
#include "clock.h"
@@ -24,6 +26,7 @@ std::string kProductNameKey = "product_name";
std::string kBuildInfoKey = "build_info";
std::string kDeviceIdKey = "device_id";
std::string kWVCdmVersionKey = "widevine_cdm_version";
std::string kOemCryptoSecurityPatchLevelKey = "oem_crypto_security_patch_level";
const unsigned char kServiceCertificateCAPublicKey[] = {
0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb4, 0xfe, 0x39,
0xc3, 0x65, 0x90, 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd,
@@ -60,6 +63,10 @@ const unsigned char kServiceCertificateCAPublicKey[] = {
0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, 0x91, 0x02, 0x03, 0x01,
0x00, 0x01};
}
const uint32_t kFourCcCbc1 = 0x63626331;
const uint32_t kFourCcCbcs = 0x63626373;
const uint32_t kFourCcCenc = 0x63656e63;
const uint32_t kFourCcCens = 0x63656e73;
namespace wvcdm {
@@ -91,7 +98,7 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
size_t length;
switch (license.key(i).type()) {
case License_KeyContainer::CONTENT:
case License_KeyContainer::OPERATOR_SESSION:
case License_KeyContainer::OPERATOR_SESSION: {
key.set_key_id(license.key(i).id());
// Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes,
// the padding will always be 16 bytes.
@@ -106,8 +113,17 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
key.set_key_control(license.key(i).key_control().key_control_block());
key.set_key_control_iv(license.key(i).key_control().iv());
}
uint32_t four_cc = kFourCcCenc;
if (license.has_protection_scheme()) {
four_cc = ntohl(license.protection_scheme());
}
if (four_cc == kFourCcCbc1 || four_cc == kFourCcCbcs)
key.set_cipher_mode(kCipherModeCbc);
else
key.set_cipher_mode(kCipherModeCtr);
key_array.push_back(key);
break;
}
case License_KeyContainer::KEY_CONTROL:
if (license.key(i).has_key_control()) {
key.set_key_control(license.key(i).key_control().key_control_block());
@@ -232,7 +248,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
LicenseRequest_ContentIdentification* content_id =
license_request.mutable_content_id();
if (init_data.is_cenc()) {
if (init_data.is_cenc() || init_data.is_hls()) {
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
content_id->mutable_cenc_id();
@@ -535,8 +551,6 @@ CdmResponseType CdmLicense::HandleKeyResponse(
server_url_ = license.policy().renewal_server_url();
}
policy_engine_->SetLicense(license);
if (license.policy().has_renew_with_client_id()) {
renew_with_client_id_ = license.policy().renew_with_client_id();
}
@@ -551,6 +565,7 @@ CdmResponseType CdmLicense::HandleKeyResponse(
it != key_array.end(); ++it) {
loaded_keys_.insert(it->key_id());
}
policy_engine_->SetLicense(license);
}
return resp;
}
@@ -612,8 +627,6 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
return LICENSE_ID_NOT_FOUND;
}
policy_engine_->UpdateLicense(license);
if (license.policy().has_renew_with_client_id()) {
renew_with_client_id_ = license.policy().renew_with_client_id();
}
@@ -637,6 +650,8 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(),
key_array.size(), &key_array[0])) {
policy_engine_->UpdateLicense(license);
return KEY_ADDED;
} else {
return REFRESH_KEYS_ERROR;
@@ -981,6 +996,11 @@ CdmResponseType CdmLicense::PrepareClientId(
client_info->set_name(kWVCdmVersionKey);
client_info->set_value(value);
}
client_info = client_id->add_client_info();
client_info->set_name(kOemCryptoSecurityPatchLevelKey);
std::stringstream ss;
ss << (uint32_t)session_->GetSecurityPatchLevel();
client_info->set_value(ss.str());
ClientIdentification_ClientCapabilities* client_capabilities =
client_id->mutable_client_capabilities();

View File

@@ -0,0 +1,285 @@
// Copyright 2016 Google Inc. All Rights Reserved.
#include "license_key_status.h"
#include <string>
#include "log.h"
namespace {
// License protocol aliases
typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer;
typedef KeyContainer::OutputProtection OutputProtection;
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
typedef ::google::protobuf::RepeatedPtrField<VideoResolutionConstraint>
ConstraintList;
// Map the HDCP protection associated with a key in the license to
// an equivalent OEMCrypto HDCP protection level
wvcdm::CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp(
const OutputProtection::HDCP& input) {
switch (input) {
case OutputProtection::HDCP_NONE:
return HDCP_NONE;
case OutputProtection::HDCP_V1:
return HDCP_V1;
case OutputProtection::HDCP_V2:
return HDCP_V2;
case OutputProtection::HDCP_V2_1:
return HDCP_V2_1;
case OutputProtection::HDCP_V2_2:
return HDCP_V2_2;
case OutputProtection::HDCP_NO_DIGITAL_OUTPUT:
return HDCP_NO_DIGITAL_OUTPUT;
default:
LOGE("ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: "
"Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT",
input);
return HDCP_NO_DIGITAL_OUTPUT;
}
}
// Returns the constraint from a set of constraints that matches the
// specified resolution, or null if none match
VideoResolutionConstraint* GetConstraintForRes(
uint32_t res, ConstraintList& constraints_from_key) {
typedef ConstraintList::pointer_iterator Iterator;
for (Iterator i = constraints_from_key.pointer_begin();
i != constraints_from_key.pointer_end(); ++i) {
VideoResolutionConstraint* constraint = *i;
if (constraint->has_min_resolution_pixels() &&
constraint->has_max_resolution_pixels() &&
res >= constraint->min_resolution_pixels() &&
res <= constraint->max_resolution_pixels()) {
return constraint;
}
}
return NULL;
}
} // namespace
namespace wvcdm {
bool LicenseKeys::IsContentKey(const std::string& key_id) {
if (keys_.count(key_id) > 0) {
return keys_[key_id]->IsContentKey();
} else {
return false;
}
}
bool LicenseKeys::CanDecryptContent(const std::string& key_id) {
if (keys_.count(key_id) > 0) {
return keys_[key_id]->CanDecryptContent();
} else {
return false;
}
}
bool LicenseKeys::GetAllowedUsage(const KeyId& key_id,
CdmKeyAllowedUsage* allowed_usage) {
if (keys_.count(key_id) > 0) {
return keys_[key_id]->GetAllowedUsage(allowed_usage);
} else {
return false;
}
}
bool LicenseKeys::ApplyStatusChange(CdmKeyStatus new_status,
bool* new_usable_keys) {
bool keys_changed = false;
bool newly_usable = false;
*new_usable_keys = false;
for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) {
bool usable;
if (it->second->ApplyStatusChange(new_status, &usable)) {
newly_usable |= usable;
keys_changed = true;
}
}
*new_usable_keys = newly_usable;
return keys_changed;
}
void LicenseKeys::ExtractKeyStatuses(CdmKeyStatusMap* content_keys) {
for (LicenseKeyStatusIterator it = keys_.begin(); it != keys_.end(); ++it) {
if (it->second->IsContentKey()) {
const KeyId key_id = it->first;
CdmKeyStatus key_status = it->second->GetKeyStatus();
(*content_keys)[key_id] = key_status;
}
}
}
bool LicenseKeys::MeetsConstraints(const KeyId& key_id) {
if (keys_.count(key_id) > 0) {
return keys_[key_id]->MeetsConstraints();
} else {
// If a Key ID is unknown to us, we don't know of any constraints for it,
// so never block decryption.
return true;
}
}
void LicenseKeys::ApplyConstraints(
uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) {
for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) {
i->second->ApplyConstraints(new_resolution, new_hdcp_level);
}
}
void LicenseKeys::SetFromLicense(
const video_widevine_server::sdk::License& license) {
this->Clear();
for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) {
const KeyContainer& key = license.key(key_index);
if (key.has_id() && (key.type() == KeyContainer::CONTENT ||
key.type() == KeyContainer::OPERATOR_SESSION)) {
const KeyId& key_id = key.id();
keys_[key_id] = new LicenseKeyStatus(key);
}
}
}
LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) :
is_content_key_(false),
key_status_(kKeyStatusInternalError),
meets_constraints_(true),
default_hdcp_level_(HDCP_NONE) {
allowed_usage_.Clear();
constraints_.Clear();
if (key.type() == KeyContainer::CONTENT) {
ParseContentKey(key);
} else if (key.type() == KeyContainer::OPERATOR_SESSION) {
ParseOperatorSessionKey(key);
}
}
void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) {
is_content_key_ = true;
if (key.has_level() &&
((key.level() == KeyContainer::HW_SECURE_DECODE) ||
(key.level() == KeyContainer::HW_SECURE_ALL))) {
allowed_usage_.decrypt_to_clear_buffer = false;
allowed_usage_.decrypt_to_secure_buffer = true;
} else {
allowed_usage_.decrypt_to_clear_buffer = true;
allowed_usage_.decrypt_to_secure_buffer = true;
}
allowed_usage_.SetValid();
if (key.video_resolution_constraints_size() > 0) {
SetConstraints(key.video_resolution_constraints());
}
if (key.has_required_protection()) {
default_hdcp_level_ =
ProtobufHdcpToOemCryptoHdcp(key.required_protection().hdcp());
}
}
void LicenseKeyStatus::ParseOperatorSessionKey(const KeyContainer& key) {
is_content_key_ = false;
if (key.has_operator_session_key_permissions()) {
OperatorSessionKeyPermissions permissions =
key.operator_session_key_permissions();
if (permissions.has_allow_encrypt())
allowed_usage_.generic_encrypt = permissions.allow_encrypt();
if (permissions.has_allow_decrypt())
allowed_usage_.generic_decrypt = permissions.allow_decrypt();
if (permissions.has_allow_sign())
allowed_usage_.generic_sign = permissions.allow_sign();
if (permissions.has_allow_signature_verify())
allowed_usage_.generic_verify = permissions.allow_signature_verify();
} else {
allowed_usage_.generic_encrypt = false;
allowed_usage_.generic_decrypt = false;
allowed_usage_.generic_sign = false;
allowed_usage_.generic_verify = false;
}
allowed_usage_.SetValid();
}
void LicenseKeys::Clear() {
for (LicenseKeyStatusIterator i = keys_.begin(); i != keys_.end(); ++i) {
delete i->second;
}
keys_.clear();
}
bool LicenseKeyStatus::CanDecryptContent() {
return is_content_key_ && key_status_ == kKeyStatusUsable;
}
bool LicenseKeyStatus::GetAllowedUsage(CdmKeyAllowedUsage* allowed_usage) {
if (NULL == allowed_usage)
return false;
*allowed_usage = allowed_usage_;
return true;
}
bool LicenseKeyStatus::ApplyStatusChange(CdmKeyStatus new_status,
bool* new_usable_key) {
*new_usable_key = false;
if (!is_content_key_) {
return false;
}
CdmKeyStatus updated_status = new_status;
if (updated_status == kKeyStatusUsable) {
if (!MeetsConstraints()) {
updated_status = kKeyStatusOutputNotAllowed;
}
}
if (key_status_ != updated_status) {
key_status_ = updated_status;
if (updated_status == kKeyStatusUsable) {
*new_usable_key = true;
}
return true;
}
return false;
}
// If the key has constraints, find the constraint that applies.
// If none found, then the constraint test fails.
// If a constraint is found, verify that the device's current HDCP
// level is sufficient. If the constraint has an HDCP setting, use it,
// If the key has no constraints, or if the constraint has no HDCP
// requirement, use the key's default HDCP setting to check against the
// device's current HDCP level.
void LicenseKeyStatus::ApplyConstraints(
uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level) {
VideoResolutionConstraint* current_constraint = NULL;
if (HasConstraints()) {
current_constraint = GetConstraintForRes(new_resolution, constraints_);
if (NULL == current_constraint) {
meets_constraints_ = false;
return;
}
}
CryptoSession::HdcpCapability desired_hdcp_level;
if (current_constraint && current_constraint->has_required_protection()) {
desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp(
current_constraint->required_protection().hdcp());
} else {
desired_hdcp_level = default_hdcp_level_;
}
meets_constraints_ = (new_hdcp_level >= desired_hdcp_level);
}
void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) {
if (!is_content_key_) {
return;
}
constraints_.Clear();
constraints_.MergeFrom(constraints);
meets_constraints_ = true;
}
} // namespace wvcdm

View File

@@ -196,6 +196,10 @@ message License {
optional bool remote_attestation_verified = 5 [default = false];
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 6;
// Protection scheme identifying the encryption algorithm. Represented as one
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
optional uint32 protection_scheme = 7;
}
enum ProtocolVersion {
@@ -268,6 +272,28 @@ message LicenseError {
optional Error error_code = 1;
}
message MetricData {
enum MetricType {
// The time spent in the 'stage', specified in microseconds.
LATENCY = 1;
// The UNIX epoch timestamp at which the 'stage' was first accessed in
// microseconds.
TIMESTAMP = 2;
}
message TypeValue {
optional MetricType type = 1;
// The value associated with 'type'. For example if type == LATENCY, the
// value would be the time in microseconds spent in this 'stage'.
optional int64 value = 2 [default = 0];
}
// 'stage' that is currently processing the SignedMessage. Required.
optional string stage_name = 1;
// metric and associated value.
repeated TypeValue metric_data = 2;
}
message RemoteAttestation {
// Encrypted ClientIdentification message containing the device remote
// attestation certificate. Required.
@@ -296,6 +322,14 @@ message SignedMessage {
// request for ChromeOS client devices operating in verified mode. Remote
// attestation challenge data is |msg| field above. Optional.
optional RemoteAttestation remote_attestation = 5;
repeated MetricData metric_data = 6;
}
message GroupKeys {
repeated License.KeyContainer key = 1;
// Byte string that identifies the group to which this license material
// belongs.
optional bytes group_id = 2;
}
// ----------------------------------------------------------------------------
@@ -427,7 +461,7 @@ message EncryptedClientIdentification {
optional bytes encrypted_client_id = 3;
// Initialization vector needed to decrypt encrypted_client_id.
optional bytes encrypted_client_id_iv = 4;
// AES-128 privacy key, encrytped with the service public public key using
// AES-128 privacy key, encrypted with the service public public key using
// RSA-OAEP.
optional bytes encrypted_privacy_key = 5;
}
@@ -547,3 +581,48 @@ message SignedCertificateStatusList {
// key using RSASSA-PSS. Required.
optional bytes signature = 2;
}
// ----------------------------------------------------------------------------
// widevine_header.proto
// ----------------------------------------------------------------------------
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Description:
// Public protocol buffer definitions for Widevine Cenc Header
// protocol.
message WidevineCencHeader {
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
// Replaced with protection_scheme.
optional Algorithm algorithm = 1 [deprecated=true];
repeated bytes key_id = 2;
// Content provider name.
optional string provider = 3;
// A content identifier, specified by content provider.
optional bytes content_id = 4;
// Track type. Acceptable values are SD, HD and AUDIO. Used to differentiate
// content keys used by an asset.
// No longer adding track_type to the PSSH since the Widevine license server
// will return keys for all allowed track types in a single license.
optional string track_type_deprecated = 5;
// The name of a registered policy to be used for this asset.
optional string policy = 6 [deprecated=true];
// Crypto period index, for media using key rotation.
optional uint32 crypto_period_index = 7;
// Optional protected context for group content. The grouped_license is a
// serialized SignedMessage.
optional bytes grouped_license = 8;
// Protection scheme identifying the encryption algorithm. Represented as one
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
optional uint32 protection_scheme = 9;
}

View File

@@ -1,173 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include "max_res_engine.h"
#include "clock.h"
#include "log.h"
namespace {
const int64_t kHdcpCheckInterval = 10;
const uint32_t kNoResolution = 0;
} // namespace
namespace wvcdm {
MaxResEngine::MaxResEngine(CryptoSession* crypto_session) {
Init(crypto_session, new Clock());
}
MaxResEngine::MaxResEngine(CryptoSession* crypto_session, Clock* clock) {
Init(crypto_session, clock);
}
MaxResEngine::~MaxResEngine() {
AutoLock lock(status_lock_);
DeleteAllKeys();
}
bool MaxResEngine::CanDecrypt(const KeyId& key_id) {
AutoLock lock(status_lock_);
if (keys_.count(key_id) > 0) {
return keys_[key_id]->can_decrypt();
} else {
// If a Key ID is unknown to us, we don't know of any constraints for it,
// so never block decryption.
return true;
}
}
void MaxResEngine::Init(CryptoSession* crypto_session, Clock* clock) {
AutoLock lock(status_lock_);
current_resolution_ = kNoResolution;
clock_.reset(clock);
next_check_time_ = clock_->GetCurrentTime();
crypto_session_ = crypto_session;
}
void MaxResEngine::SetLicense(
const video_widevine_server::sdk::License& license) {
AutoLock lock(status_lock_);
DeleteAllKeys();
for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) {
const KeyContainer& key = license.key(key_index);
if (key.type() == KeyContainer::CONTENT && key.has_id() &&
key.video_resolution_constraints_size() > 0) {
const ConstraintList& constraints = key.video_resolution_constraints();
const KeyId& key_id = key.id();
if (key.has_required_protection()) {
keys_[key_id] =
new KeyStatus(constraints, key.required_protection().hdcp());
} else {
keys_[key_id] = new KeyStatus(constraints);
}
}
}
}
void MaxResEngine::SetResolution(uint32_t width, uint32_t height) {
AutoLock lock(status_lock_);
current_resolution_ = width * height;
}
void MaxResEngine::OnTimerEvent() {
AutoLock lock(status_lock_);
int64_t current_time = clock_->GetCurrentTime();
if (!keys_.empty() && current_resolution_ != kNoResolution &&
current_time >= next_check_time_) {
CryptoSession::HdcpCapability current_hdcp_level;
CryptoSession::HdcpCapability ignored;
if (!crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored)) {
current_hdcp_level = HDCP_NONE;
}
for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) {
i->second->Update(current_resolution_, current_hdcp_level);
}
next_check_time_ = current_time + kHdcpCheckInterval;
}
}
void MaxResEngine::DeleteAllKeys() {
// This helper method assumes that status_lock_ is already held.
for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) delete i->second;
keys_.clear();
}
MaxResEngine::KeyStatus::KeyStatus(const ConstraintList& constraints)
: default_hdcp_level_(HDCP_NONE) {
Init(constraints);
}
MaxResEngine::KeyStatus::KeyStatus(
const ConstraintList& constraints,
const OutputProtection::HDCP& default_hdcp_level)
: default_hdcp_level_(ProtobufHdcpToOemCryptoHdcp(default_hdcp_level)) {
Init(constraints);
}
void MaxResEngine::KeyStatus::Init(const ConstraintList& constraints) {
constraints_.Clear();
constraints_.MergeFrom(constraints);
can_decrypt_ = true;
}
void MaxResEngine::KeyStatus::Update(
uint32_t res, CryptoSession::HdcpCapability current_hdcp_level) {
VideoResolutionConstraint* current_constraint = GetConstraintForRes(res);
if (current_constraint == NULL) {
can_decrypt_ = false;
return;
}
CryptoSession::HdcpCapability desired_hdcp_level;
if (current_constraint->has_required_protection()) {
desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp(
current_constraint->required_protection().hdcp());
} else {
desired_hdcp_level = default_hdcp_level_;
}
can_decrypt_ = (current_hdcp_level >= desired_hdcp_level);
}
MaxResEngine::VideoResolutionConstraint*
MaxResEngine::KeyStatus::GetConstraintForRes(uint32_t res) {
typedef ConstraintList::pointer_iterator Iterator;
for (Iterator i = constraints_.pointer_begin();
i != constraints_.pointer_end(); ++i) {
VideoResolutionConstraint* constraint = *i;
if (constraint->has_min_resolution_pixels() &&
constraint->has_max_resolution_pixels() &&
res >= constraint->min_resolution_pixels() &&
res <= constraint->max_resolution_pixels()) {
return constraint;
}
}
return NULL;
}
CryptoSession::HdcpCapability
MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp(
const OutputProtection::HDCP& input) {
switch (input) {
case OutputProtection::HDCP_NONE:
return HDCP_NONE;
case OutputProtection::HDCP_V1:
return HDCP_V1;
case OutputProtection::HDCP_V2:
return HDCP_V2;
case OutputProtection::HDCP_V2_1:
return HDCP_V2_1;
case OutputProtection::HDCP_V2_2:
return HDCP_V2_2;
case OutputProtection::HDCP_NO_DIGITAL_OUTPUT:
return HDCP_NO_DIGITAL_OUTPUT;
default:
LOGE("MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp: "
"Unknown HDCP Level");
return HDCP_NO_DIGITAL_OUTPUT;
}
}
} // wvcdm

View File

@@ -38,6 +38,10 @@ uint32_t OEMCrypto_APIVersion(SecurityLevel level) {
return ::OEMCrypto_APIVersion();
}
uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level) {
return ::OEMCrypto_Security_Patch_Level();
}
const char* OEMCrypto_SecurityLevel(SecurityLevel level) {
return ::OEMCrypto_SecurityLevel();
}

View File

@@ -0,0 +1,67 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Wrapper of OEMCrypto APIs for platforms that support Level 1 only.
// This should be used when liboemcrypto.so is linked with the CDM code at
// compile time.
//
// Defines APIs introduced in newer version (v11) which is not available in v10
// to allow an older oemcrypto implementation to be linked with CDM.
#include "OEMCryptoCENC.h"
extern "C" OEMCryptoResult OEMCrypto_DecryptCTR_V10(
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
bool is_encrypted, const uint8_t* iv, size_t block_offset,
const OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags);
typedef struct {
const uint8_t* key_id;
size_t key_id_length;
const uint8_t* key_data_iv;
const uint8_t* key_data;
size_t key_data_length;
const uint8_t* key_control_iv;
const uint8_t* key_control;
} OEMCrypto_KeyObject_V9;
extern "C" OEMCryptoResult OEMCrypto_LoadKeys_V9_or_V10(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length,
const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys,
size_t num_keys, const OEMCrypto_KeyObject_V9* key_array,
const uint8_t* pst, size_t pst_length);
extern "C" uint8_t OEMCrypto_Security_Patch_Level() { return 0; }
extern "C" OEMCryptoResult OEMCrypto_DecryptCENC(
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
bool is_encrypted, const uint8_t* iv, size_t block_offset,
OEMCrypto_DestBufferDesc* out_buffer,
const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) {
return OEMCrypto_DecryptCTR_V10(session, data_addr, data_length, is_encrypted,
iv, block_offset, out_buffer,
subsample_flags);
}
extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length,
const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys,
size_t num_keys, const OEMCrypto_KeyObject* key_array, const uint8_t* pst,
size_t pst_length) {
OEMCrypto_KeyObject_V9 key_array_v9[num_keys];
for (size_t key_index = 0; key_index < num_keys; key_index++) {
key_array_v9[key_index].key_id = key_array[key_index].key_id;
key_array_v9[key_index].key_id_length = key_array[key_index].key_id_length;
key_array_v9[key_index].key_data_iv = key_array[key_index].key_data_iv;
key_array_v9[key_index].key_data = key_array[key_index].key_data;
key_array_v9[key_index].key_data_length =
key_array[key_index].key_data_length;
key_array_v9[key_index].key_control_iv =
key_array[key_index].key_control_iv;
key_array_v9[key_index].key_control = key_array[key_index].key_control;
}
return OEMCrypto_LoadKeys_V9_or_V10(
session, message, message_length, signature, signature_length,
enc_mac_keys_iv, enc_mac_keys, num_keys, key_array_v9, pst, pst_length);
}

View File

@@ -3,10 +3,7 @@
#include "policy_engine.h"
#include <limits.h>
#include <sstream>
#include <string>
#include <vector>
#include "clock.h"
#include "log.h"
@@ -17,6 +14,13 @@
using video_widevine_server::sdk::License;
namespace {
const int64_t kHdcpCheckInterval = 10;
const uint32_t kNoResolution = 0;
} // namespace
namespace wvcdm {
PolicyEngine::PolicyEngine(CdmSessionId session_id,
@@ -32,18 +36,43 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
policy_max_duration_seconds_(0),
session_id_(session_id),
event_listener_(event_listener),
max_res_engine_(new MaxResEngine(crypto_session)),
clock_(new Clock) {}
license_keys_(new LicenseKeys),
clock_(new Clock) {
InitDevice(crypto_session);
}
PolicyEngine::~PolicyEngine() {}
bool PolicyEngine::CanDecrypt(const KeyId& key_id) {
if (keys_status_.find(key_id) == keys_status_.end()) {
if (license_keys_->IsContentKey(key_id)) {
return license_keys_->CanDecryptContent(key_id);
} else {
LOGE("PolicyEngine::CanDecrypt Key '%s' not in license.",
b2a_hex(key_id).c_str());
return false;
}
return keys_status_[key_id] == kKeyStatusUsable;
}
void PolicyEngine::InitDevice(CryptoSession* crypto_session) {
current_resolution_ = kNoResolution;
next_device_check_ = 0;
crypto_session_ = crypto_session;
}
void PolicyEngine::CheckDevice(int64_t current_time) {
if (current_time < next_device_check_) {
return;
}
if (!license_keys_->Empty() && current_resolution_ != kNoResolution) {
CryptoSession::HdcpCapability current_hdcp_level;
CryptoSession::HdcpCapability ignored;
if (!crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored)) {
current_hdcp_level = HDCP_NONE;
}
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
next_device_check_ = current_time + kHdcpCheckInterval;
}
}
void PolicyEngine::OnTimerEvent() {
@@ -57,7 +86,8 @@ void PolicyEngine::OnTimerEvent() {
return;
}
max_res_engine_->OnTimerEvent();
// Check device conditions that affect playability (HDCP, resolution)
CheckDevice(current_time);
bool renewal_needed = false;
@@ -110,17 +140,8 @@ void PolicyEngine::SetLicense(const License& license) {
license_id_.Clear();
license_id_.CopyFrom(license.id());
policy_.Clear();
// Extract content key ids.
keys_status_.clear();
for (int key_index = 0; key_index < license.key_size(); ++key_index) {
const License::KeyContainer& key = license.key(key_index);
if (key.type() == License::KeyContainer::CONTENT && key.has_id())
keys_status_[key.id()] = kKeyStatusInternalError;
}
license_keys_->SetFromLicense(license);
UpdateLicense(license);
max_res_engine_->SetLicense(license);
}
void PolicyEngine::SetLicenseForRelease(const License& license) {
@@ -130,7 +151,6 @@ void PolicyEngine::SetLicenseForRelease(const License& license) {
// Expire any old keys.
NotifyKeysChange(kKeyStatusExpired);
UpdateLicense(license);
}
@@ -220,7 +240,7 @@ void PolicyEngine::DecryptionEvent() {
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
max_res_engine_->SetResolution(width, height);
SetDeviceResolution(width, height);
}
void PolicyEngine::NotifySessionExpiration() {
@@ -228,35 +248,46 @@ void PolicyEngine::NotifySessionExpiration() {
NotifyKeysChange(kKeyStatusExpired);
}
CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) {
std::stringstream ss;
int64_t current_time = clock_->GetCurrentTime();
if (license_state_ == kLicenseStateInitial) {
key_info->clear();
query_response->clear();
return NO_ERROR;
}
(*key_info)[QUERY_KEY_LICENSE_TYPE] =
(*query_response)[QUERY_KEY_LICENSE_TYPE] =
license_id_.type() == video_widevine_server::sdk::STREAMING
? QUERY_VALUE_STREAMING
: QUERY_VALUE_OFFLINE;
(*key_info)[QUERY_KEY_PLAY_ALLOWED] =
(*query_response)[QUERY_KEY_PLAY_ALLOWED] =
policy_.can_play() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
(*key_info)[QUERY_KEY_PERSIST_ALLOWED] =
(*query_response)[QUERY_KEY_PERSIST_ALLOWED] =
policy_.can_persist() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
(*key_info)[QUERY_KEY_RENEW_ALLOWED] =
(*query_response)[QUERY_KEY_RENEW_ALLOWED] =
policy_.can_renew() ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
ss << GetLicenseDurationRemaining(current_time);
(*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
(*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
ss.str("");
ss << GetPlaybackDurationRemaining(current_time);
(*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
(*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
(*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
(*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
return NO_ERROR;
}
CdmResponseType PolicyEngine::QueryKeyAllowedUsage(
const KeyId& key_id, CdmKeyAllowedUsage* key_usage) {
if (NULL == key_usage) {
return INVALID_PARAMETERS_ENG_12;
}
if (license_keys_->GetAllowedUsage(key_id, key_usage)) {
return NO_ERROR;
}
return KEY_NOT_FOUND_1;
}
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
if (playback_start_time_ == 0) return false;
@@ -272,6 +303,12 @@ bool PolicyEngine::GetSecondsSinceLastPlayed(
return (*seconds_since_last_played >= 0) ? true : false;
}
int64_t PolicyEngine::GetLicenseOrPlaybackDurationRemaining() {
int64_t current_time = clock_->GetCurrentTime();
return std::min(GetLicenseDurationRemaining(current_time),
GetPlaybackDurationRemaining(current_time));
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time) {
playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0;
@@ -345,25 +382,14 @@ bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) {
}
void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) {
bool keys_changed = false;
bool keys_changed;
bool has_new_usable_key = false;
for (std::map<KeyId, CdmKeyStatus>::iterator it = keys_status_.begin();
it != keys_status_.end(); ++it) {
const KeyId key_id = it->first;
CdmKeyStatus& key_status = it->second;
CdmKeyStatus updated_status = new_status;
if (updated_status == kKeyStatusUsable) {
if (!max_res_engine_->CanDecrypt(key_id))
updated_status = kKeyStatusOutputNotAllowed;
}
if (key_status != updated_status) {
key_status = updated_status;
if (updated_status == kKeyStatusUsable) has_new_usable_key = true;
keys_changed = true;
}
}
if (keys_changed && event_listener_) {
event_listener_->OnSessionKeysChange(session_id_, keys_status_,
keys_changed = license_keys_->ApplyStatusChange(new_status,
&has_new_usable_key);
if (event_listener_ && keys_changed) {
CdmKeyStatusMap content_keys;
license_keys_->ExtractKeyStatuses(&content_keys);
event_listener_->OnSessionKeysChange(session_id_, content_keys,
has_new_usable_key);
}
}
@@ -381,8 +407,4 @@ void PolicyEngine::NotifyExpirationUpdate() {
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
void PolicyEngine::set_max_res_engine(MaxResEngine* max_res_engine) {
max_res_engine_.reset(max_res_engine);
}
} // wvcdm
} // namespace wvcdm

View File

@@ -10,6 +10,7 @@
#include <iostream>
#include <vector>
#include <modp_b64.h>
#include <modp_b64w.h>
#include "log.h"
@@ -78,6 +79,26 @@ std::string b2a_hex(const std::string& byte) {
byte.length());
}
// Encode for standard base64 encoding (RFC4648).
std::string Base64Encode(const std::vector<uint8_t>& bin_input) {
if (bin_input.empty()) {
return std::string();
}
int in_size = bin_input.size();
std::string b64_output(modp_b64_encode_len(in_size), 0);
int out_size = modp_b64_encode(
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
if (out_size == -1) {
LOGE("Base64Encode failed");
return std::string();
}
b64_output.resize(out_size);
return b64_output;
}
// Filename-friendly base64 encoding (RFC4648), commonly referred to
// as Base64WebSafeEncode.
//
@@ -111,6 +132,25 @@ std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
return b64_output;
}
// Decode for standard base64 encoding (RFC4648).
std::vector<uint8_t> Base64Decode(const std::string& b64_input) {
if (b64_input.empty()) {
return std::vector<uint8_t>();
}
int in_size = b64_input.size();
std::vector<uint8_t> bin_output(modp_b64_decode_len(in_size), 0);
int out_size = modp_b64_decode(reinterpret_cast<char*>(&bin_output[0]),
b64_input.data(), in_size);
if (out_size == -1) {
LOGE("Base64Decode failed");
return std::vector<uint8_t>(0);
}
bin_output.resize(out_size);
return bin_output;
}
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
// as Base64WebSafeDecode.
std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
@@ -157,18 +197,6 @@ std::string IntToString(int value) {
return out_string;
}
std::string UintToString(unsigned int value) {
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
// So round up to allocate 3 output characters per byte.
const int kOutputBufSize = 3 * sizeof(unsigned int);
char buffer[kOutputBufSize];
memset(buffer, 0, kOutputBufSize);
snprintf(buffer, kOutputBufSize, "%u", value);
std::string out_string(buffer);
return out_string;
}
int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order)
union {
uint32_t array[2];