These are a set of CLs merged from the wv cdm repo to the android repo. * Android build fixes Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/36322 ] * Address android compilation errors and warnings Author: Rahul Frias <rfrias@google.com> [ Merge of http://go/wvgerrit/36300 ] * Gyp cleanup and OpenSSL v10.1 support. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/36001 ] OpenSSL 10.1 has a small number of incompatible changes. A desktop system upgrade exposed some issue in the build scripts. Specifically, the linux build was using both third_party/protobufs (2.6.1) and the version installed on the system (3.0 in this case). The linux cdm.gyp depended on cdm/cdm.gyp which caused that plus some additional issues. These changes are necessary to support g++ version: g++ (Debian 6.3.0-18) 6.3.0 20170516 Also did some cosmetic rework on run_current_tests to make it easier to figure out what is going on when something fails. Also tweaked some of the compiler settings for g++ support (revisit this later). * Refactored Service Certificate encryption to allow encryption of arbitrary data. Author: Thomas Inskip <tinskip@google.com> [ Merge of http://go/wvgerrit/36141 ] * Send cdm test requests to UAT. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/36221 ] This change resolves the all of the CdmDecryptTest/CdmTestWithDecryptParam.DecryptToClearBuffer tests. The license servers will return different keys and keyids. Sending the request to staging returned key ids and keys that were not matching what was expected in the unit tests. * Fix for building L3 OEMCrypto with clang and libc++ Author: yucliu <yucliu@google.com> [ Merge of http://go/wvgerrit/35740 ] 1. Include <time.h> for time(time_t*). 2. Create endian check union on stack. Clang may create const union somewhere else, which may cause crash. * Remove error result when a sublicense session does not exist. This is not considered an error. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/36080 ] * Set default mock handler for GetSupportedCertificateTypes for all unit tests and removed the use of StrictMock from MockCryptoSession. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/35922 ] The handler for this was only set for one test and resulted in a number of failures. * Set default handler for GetHdcpCapabilities. For now the default action is to call the real GetHdcpCapabilities of crypto_session. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/36140 ] I also changed the mock to a NiceMock to silence responses to unexpected calls to GetHdcpCapabilities. The default handler can be overridden as needed in the individual tests. This resolves the policy engine test failures. * Finalize merge of cdm_partner_3.4 to master. Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/35360 ] This is the final set of updates to merge all v3.4.1 changes into master. * Embedded license: Sublicense rotation. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/35360 ] Handle sublicense rotation event. * Embedded license: Initial license phase. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/34280 ] Initial license phase - key loading subsession. * Embedded license: generate session data. Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/33722 ] Generate session data and add it to the license request for any embedded license material. * Resolve missing symbol when building cd-cdm Author: Jeff Fore <jfore@google.com> [ Merge of http://go/wvgerrit/35840 ] * C++11: Replace OVERRIDE def with override keyword Author: Gene Morgan <gmorgan@google.com> [ Merge of http://go/wvgerrit/35400 ] BUG: 71650075 Test: Not currently passing. Will be addressed in a subsequent commit in the chain. Change-Id: I37d0cb17f255ac6389030047d616ad69f895748c
1205 lines
40 KiB
C++
1205 lines
40 KiB
C++
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
|
|
#include "license.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "clock.h"
|
|
#include "cdm_session.h"
|
|
#include "crypto_key.h"
|
|
#include "crypto_session.h"
|
|
#include "device_files.h"
|
|
#include "log.h"
|
|
#include "policy_engine.h"
|
|
#include "privacy_crypto.h"
|
|
#include "properties.h"
|
|
#include "service_certificate.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace {
|
|
const std::string kCompanyNameKey = "company_name";
|
|
const std::string kModelNameKey = "model_name";
|
|
const std::string kArchitectureNameKey = "architecture_name";
|
|
const std::string kDeviceNameKey = "device_name";
|
|
const std::string kProductNameKey = "product_name";
|
|
const std::string kBuildInfoKey = "build_info";
|
|
const std::string kDeviceIdKey = "device_id";
|
|
const std::string kWVCdmVersionKey = "widevine_cdm_version";
|
|
const std::string kOemCryptoSecurityPatchLevelKey =
|
|
"oem_crypto_security_patch_level";
|
|
|
|
const uint32_t kFourCcCbc1 = 0x63626331;
|
|
const uint32_t kFourCcCbcs = 0x63626373;
|
|
const uint32_t kFourCcLittleEndianCbc1 = 0x31636263;
|
|
const uint32_t kFourCcLittleEndianCbcs = 0x73636263;
|
|
const uint32_t kFourCcCenc = 0x63656e63;
|
|
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
|
|
// Protobuf generated classes.
|
|
using video_widevine::ClientIdentification;
|
|
using video_widevine::ClientIdentification_ClientCapabilities;
|
|
using video_widevine::ClientIdentification_NameValue;
|
|
using video_widevine::DrmDeviceCertificate;
|
|
using video_widevine::EncryptedClientIdentification;
|
|
using video_widevine::License;
|
|
using video_widevine::LicenseError;
|
|
using video_widevine::LicenseIdentification;
|
|
using video_widevine::LicenseRequest;
|
|
using video_widevine::LicenseRequest_ContentIdentification;
|
|
using video_widevine::LicenseRequest_ContentIdentification_CencDeprecated;
|
|
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
|
|
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
|
|
using video_widevine::License_KeyContainer;
|
|
using video_widevine::SignedDrmDeviceCertificate;
|
|
using video_widevine::SignedMessage;
|
|
|
|
static std::vector<CryptoKey> ExtractSubSessionKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
// Extract sub session key(s)
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::SUB_SESSION:
|
|
key.set_key_data(license.key(i).key());
|
|
key.set_key_data_iv(license.key(i).iv());
|
|
key.set_key_id(license.key(i).id());
|
|
key.set_track_label(license.key(i).track_label());
|
|
key_array.push_back(key);
|
|
break;
|
|
|
|
default:
|
|
// Ignore all but SUB_SESSION key types.
|
|
break;
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
// Extract content key(s)
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
size_t length;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::CONTENT:
|
|
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.
|
|
if (license.key(i).key().size() > 16) {
|
|
length = license.key(i).key().size() - 16;
|
|
} else {
|
|
length = 0;
|
|
}
|
|
key.set_key_data(license.key(i).key().substr(0, length));
|
|
key.set_key_data_iv(license.key(i).iv());
|
|
if (license.key(i).has_key_control()) {
|
|
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 = license.protection_scheme();
|
|
}
|
|
key.set_track_label(license.key(i).track_label());
|
|
switch (four_cc) {
|
|
// b/30713238: Android N assumed that the "protection scheme" Four
|
|
// CC code, after being extracted from the protobuf, was host byte
|
|
// order dependent. Later versions do not assume this, and thus,
|
|
// for backwards compatibility, must support both byte orders.
|
|
case kFourCcCbc1:
|
|
case kFourCcCbcs:
|
|
case kFourCcLittleEndianCbc1:
|
|
case kFourCcLittleEndianCbcs:
|
|
key.set_cipher_mode(kCipherModeCbc);
|
|
break;
|
|
default:
|
|
key.set_cipher_mode(kCipherModeCtr);
|
|
break;
|
|
}
|
|
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());
|
|
if (license.key(i).key_control().has_iv()) {
|
|
key.set_key_control_iv(license.key(i).key_control().iv());
|
|
}
|
|
key_array.push_back(key);
|
|
}
|
|
break;
|
|
default:
|
|
// Ignore SIGNING and SUB_SESSION key types as they are not content
|
|
// related.
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::vector<CryptoKey> sub_session_keys = ExtractSubSessionKeys(license);
|
|
// Match the track label from the key arrays and add sub_license_key_id to
|
|
// the content key array.
|
|
LOGV("Received %d subsession keys", sub_session_keys.size());
|
|
if (!sub_session_keys.empty()) {
|
|
for (size_t i = 0; i < key_array.size(); ++i) {
|
|
if (key_array[i].track_label().empty()) continue;
|
|
for (size_t x = 0; x < sub_session_keys.size(); ++x) {
|
|
if (sub_session_keys[x].track_label() == key_array[i].track_label()) {
|
|
key_array[i].set_sub_session_key_id(sub_session_keys[x].key_id());
|
|
key_array[i].set_sub_session_key(sub_session_keys[x].key_data());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
|
: crypto_session_(NULL),
|
|
policy_engine_(NULL),
|
|
session_id_(session_id),
|
|
initialized_(false),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false),
|
|
clock_(new Clock()) {}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock)
|
|
: crypto_session_(NULL),
|
|
policy_engine_(NULL),
|
|
session_id_(session_id),
|
|
initialized_(false),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false) {
|
|
clock_.reset(clock);
|
|
}
|
|
|
|
CdmLicense::~CdmLicense() {}
|
|
|
|
bool CdmLicense::Init(
|
|
ServiceCertificate* service_certificate, const std::string& client_token,
|
|
CdmClientTokenType client_token_type, const std::string& device_id,
|
|
CryptoSession* session, PolicyEngine* policy_engine) {
|
|
if (clock_.get() == NULL) {
|
|
LOGE("CdmLicense::Init: clock parameter not provided");
|
|
return false;
|
|
}
|
|
if (session_id_.empty()) {
|
|
LOGE("CdmLicense::Init: empty session id provided");
|
|
return false;
|
|
}
|
|
if (client_token.size() == 0) {
|
|
LOGE("CdmLicense::Init: empty client token provided");
|
|
return false;
|
|
}
|
|
if (session == NULL || !session->IsOpen()) {
|
|
LOGE("CdmLicense::Init: crypto session not provided or not open");
|
|
return false;
|
|
}
|
|
if (policy_engine == NULL) {
|
|
LOGE("CdmLicense::Init: no policy engine provided");
|
|
return false;
|
|
}
|
|
|
|
service_certificate_ = service_certificate;
|
|
client_token_ = client_token;
|
|
client_token_type_ = client_token_type;
|
|
device_id_ = device_id;
|
|
crypto_session_ = session;
|
|
policy_engine_ = policy_engine;
|
|
initialized_ = true;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyRequest(
|
|
const InitializationData& init_data, CdmLicenseType license_type,
|
|
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_4;
|
|
}
|
|
if (!init_data.is_supported()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: unsupported init data type (%s)",
|
|
init_data.type().c_str());
|
|
return INVALID_PARAMETERS_LIC_3;
|
|
}
|
|
if (init_data.IsEmpty()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: empty init data provided");
|
|
return INVALID_PARAMETERS_LIC_4;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: no signed request provided");
|
|
return INVALID_PARAMETERS_LIC_6;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: no server url provided");
|
|
return INVALID_PARAMETERS_LIC_7;
|
|
}
|
|
|
|
// If privacy mode, must have service certificate
|
|
if (Properties::UsePrivacyMode(session_id_) &&
|
|
!service_certificate_->has_certificate()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: failure with privacy mode - "
|
|
"no service certificate.");
|
|
return PRIVACY_MODE_ERROR_1;
|
|
}
|
|
|
|
std::string request_id;
|
|
crypto_session_->GenerateRequestId(&request_id);
|
|
|
|
LicenseRequest license_request;
|
|
CdmResponseType status;
|
|
status = PrepareClientId(app_parameters, &license_request);
|
|
if (NO_ERROR != status) return status;
|
|
|
|
status =
|
|
PrepareContentId(init_data, license_type, request_id, &license_request);
|
|
if (NO_ERROR != status) return status;
|
|
|
|
license_request.set_type(LicenseRequest::NEW);
|
|
|
|
license_request.set_request_time(clock_->GetCurrentTime());
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!crypto_session_->GenerateNonce(&nonce)) {
|
|
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
|
|
|
// Prepare the request for any embedded keys that may exist in the
|
|
// initialization data.
|
|
std::vector<video_widevine::SubLicense> embedded_key_data =
|
|
init_data.ExtractEmbeddedKeys();
|
|
for (size_t i = 0; i < embedded_key_data.size(); ++i) {
|
|
bool exists = false;
|
|
if (!crypto_session_->GenerateSubSessionNonce(
|
|
embedded_key_data[i].sub_session_key_id(), &exists, &nonce)) {
|
|
if (exists) {
|
|
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
|
|
}
|
|
}
|
|
SignedMessage signed_sub_license;
|
|
License_KeyContainer keyc;
|
|
|
|
// Parse the sub license for this track to extract the label.
|
|
if (!signed_sub_license.ParseFromString(embedded_key_data[i].key_msg()) ||
|
|
!keyc.ParseFromString(signed_sub_license.msg()) ||
|
|
keyc.track_label().empty()) {
|
|
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
|
}
|
|
|
|
LicenseRequest::SubSessionData* sub_session_data =
|
|
license_request.add_sub_session_data();
|
|
sub_session_data->set_sub_session_key_id(
|
|
embedded_key_data[i].sub_session_key_id());
|
|
sub_session_data->set_nonce(nonce);
|
|
sub_session_data->set_track_label(keyc.track_label());
|
|
}
|
|
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
if (Properties::use_certificates_as_identification())
|
|
key_request_ = serialized_license_req;
|
|
|
|
// Derive signing and encryption keys and construct signature.
|
|
std::string license_request_signature;
|
|
if (!crypto_session_->PrepareRequest(serialized_license_req, false,
|
|
&license_request_signature)) {
|
|
signed_request->clear();
|
|
return LICENSE_REQUEST_SIGNING_ERROR;
|
|
}
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: License request signature empty");
|
|
signed_request->clear();
|
|
return EMPTY_LICENSE_REQUEST;
|
|
}
|
|
|
|
// Put serialized license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
|
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
|
CdmSession* cdm_session, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_1;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided");
|
|
return INVALID_PARAMETERS_LIC_1;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided");
|
|
return INVALID_PARAMETERS_LIC_2;
|
|
}
|
|
|
|
if (is_renewal && !policy_engine_->CanRenew()) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: license renewal prohibited");
|
|
return LICENSE_RENEWAL_PROHIBITED;
|
|
}
|
|
|
|
if (renew_with_client_id_) {
|
|
if (Properties::UsePrivacyMode(session_id_) &&
|
|
!service_certificate_->has_certificate()) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: failure with privacy mode - "
|
|
"no service certificate.");
|
|
return PRIVACY_MODE_ERROR_2;
|
|
}
|
|
}
|
|
|
|
LicenseRequest license_request;
|
|
if (is_renewal)
|
|
license_request.set_type(LicenseRequest::RENEWAL);
|
|
else
|
|
license_request.set_type(LicenseRequest::RELEASE);
|
|
|
|
license_request.set_request_time(clock_->GetCurrentTime());
|
|
|
|
if (renew_with_client_id_) {
|
|
CdmResponseType status = PrepareClientId(app_parameters, &license_request);
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
|
license_request.mutable_content_id()->mutable_existing_license();
|
|
LicenseIdentification license_id = policy_engine_->license_id();
|
|
current_license->mutable_license_id()->CopyFrom(license_id);
|
|
|
|
int64_t seconds_since_started, seconds_since_last_played;
|
|
CryptoSession::UsageDurationStatus usage_duration_status =
|
|
CryptoSession::kUsageDurationsInvalid;
|
|
if (!provider_session_token_.empty()) {
|
|
if (!is_renewal) {
|
|
CdmResponseType status =
|
|
crypto_session_->DeactivateUsageInformation(provider_session_token_);
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
// TODO(rfrias): Refactor to avoid needing to call CdmSession
|
|
if (cdm_session &&
|
|
cdm_session->get_usage_support_type() == kUsageEntrySupport) {
|
|
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
std::string usage_report;
|
|
CdmResponseType status = crypto_session_->GenerateUsageReport(
|
|
provider_session_token_, &usage_report, &usage_duration_status,
|
|
&seconds_since_started, &seconds_since_last_played);
|
|
if (!is_renewal) {
|
|
if (NO_ERROR == status)
|
|
current_license->set_session_usage_table_entry(usage_report);
|
|
else
|
|
return GENERATE_USAGE_REPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid != usage_duration_status) {
|
|
if (policy_engine_->GetSecondsSinceStarted(&seconds_since_started) &&
|
|
policy_engine_->GetSecondsSinceLastPlayed(&seconds_since_last_played))
|
|
usage_duration_status = CryptoSession::kUsageDurationsValid;
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid == usage_duration_status) {
|
|
current_license->set_seconds_since_started(seconds_since_started);
|
|
current_license->set_seconds_since_last_played(seconds_since_last_played);
|
|
}
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!crypto_session_->GenerateNonce(&nonce)) {
|
|
return LICENSE_RENEWAL_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce);
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
// Construct signature.
|
|
std::string license_request_signature;
|
|
if (!crypto_session_->PrepareRenewalRequest(serialized_license_req,
|
|
&license_request_signature))
|
|
return LICENSE_RENEWAL_SIGNING_ERROR;
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE(
|
|
"CdmLicense::PrepareKeyUpdateRequest: empty license request"
|
|
" signature");
|
|
return EMPTY_LICENSE_RENEWAL;
|
|
}
|
|
|
|
// Put serialize license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyResponse(
|
|
const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::HandleKeyResponse: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_2;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: empty license response");
|
|
return EMPTY_LICENSE_RESPONSE_1;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: unable to parse signed license"
|
|
" response");
|
|
return INVALID_LICENSE_RESPONSE;
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: unrecognized signed message type: %d",
|
|
signed_response.type());
|
|
return INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: license response is not signed");
|
|
return LICENSE_RESPONSE_NOT_SIGNED;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("CdmLicense::HandleKeyResponse: unable to parse license response");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_1;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: no session keys present");
|
|
return SESSION_KEYS_NOT_FOUND;
|
|
}
|
|
if (!crypto_session_->GenerateDerivedKeys(key_request_,
|
|
signed_response.session_key()))
|
|
return GENERATE_DERIVED_KEYS_ERROR;
|
|
}
|
|
|
|
// Extract mac key
|
|
std::string mac_key_iv;
|
|
std::string mac_key;
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
if (license.key(i).type() == License_KeyContainer::SIGNING) {
|
|
mac_key_iv.assign(license.key(i).iv());
|
|
|
|
// Strip off PKCS#5 padding
|
|
mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE);
|
|
}
|
|
}
|
|
if (license.policy().can_renew() ||
|
|
(mac_key_iv.size() != 0 || mac_key.size() != 0)) {
|
|
if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: mac key/iv size error"
|
|
"(key/iv size expected: %d/%d, actual: %d/%d",
|
|
MAC_KEY_SIZE, KEY_IV_SIZE, mac_key.size(), mac_key_iv.size());
|
|
return KEY_SIZE_ERROR;
|
|
}
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
if (!key_array.size()) {
|
|
LOGE("CdmLicense::HandleKeyResponse : No content keys.");
|
|
return NO_CONTENT_KEY;
|
|
}
|
|
|
|
if (license.has_srm_update()) crypto_session_->LoadSrm(license.srm_update());
|
|
|
|
if (license.id().type() == video_widevine::OFFLINE &&
|
|
license.policy().can_persist())
|
|
is_offline_ = true;
|
|
|
|
if (license.id().has_provider_session_token())
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
|
|
LOGV("provider_session_token: %s", provider_session_token_.empty() ?
|
|
"N/A" : provider_session_token_.c_str());
|
|
|
|
if (license.policy().has_renewal_server_url()) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
CdmResponseType resp = crypto_session_->LoadKeys(
|
|
signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key,
|
|
key_array, provider_session_token_, license.srm_requirement());
|
|
|
|
if (KEY_ADDED == resp) {
|
|
loaded_keys_.clear();
|
|
for (std::vector<CryptoKey>::iterator it = key_array.begin();
|
|
it != key_array.end(); ++it) {
|
|
loaded_keys_.insert(it->key_id());
|
|
}
|
|
policy_engine_->SetLicense(license);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
|
bool is_renewal, const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_3;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse : Empty license response.");
|
|
return EMPTY_LICENSE_RESPONSE_2;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_2;
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE(
|
|
"CdmLicense::HandleKeyUpdateResponse: unrecognized signed message "
|
|
"type: %d",
|
|
signed_response.type());
|
|
return INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: signature missing");
|
|
return SIGNATURE_NOT_FOUND;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyUpdateResponse: Unable to parse license"
|
|
" from signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_3;
|
|
}
|
|
|
|
if (!license.has_id()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: license id not present");
|
|
return LICENSE_ID_NOT_FOUND;
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
if (!is_renewal) {
|
|
if (!license.id().has_provider_session_token()) return KEY_ADDED;
|
|
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
CdmResponseType status = crypto_session_->ReleaseUsageInformation(
|
|
signed_response.msg(), signed_response.signature(),
|
|
provider_session_token_);
|
|
return (NO_ERROR == status) ? KEY_ADDED : status;
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url() &&
|
|
license.policy().renewal_server_url().size() > 0) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
|
|
if (crypto_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;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleSubLicense(
|
|
const InitializationData& init_data) {
|
|
std::vector<video_widevine::SubLicense> subkeys =
|
|
init_data.ExtractEmbeddedKeys();
|
|
std::set<KeyId> loaded_keys;
|
|
// Build a license with the rotated keys.
|
|
License license;
|
|
for (size_t i = 0; i < subkeys.size(); ++i) {
|
|
SignedMessage sm;
|
|
if (!sm.ParseFromString(subkeys[i].key_msg())) {
|
|
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
|
}
|
|
License_KeyContainer keyc;
|
|
if (!keyc.ParseFromString(sm.msg())) {
|
|
return LICENSE_REQUEST_INVALID_SUBLICENSE;
|
|
}
|
|
std::vector<CryptoKey> keys;
|
|
keys.resize(1);
|
|
keys[0].set_key_id(keyc.id());
|
|
keys[0].set_key_data(keyc.key());
|
|
keys[0].set_key_data_iv(keyc.iv());
|
|
keys[0].set_key_control(keyc.key_control().key_control_block());
|
|
keys[0].set_key_control_iv(keyc.key_control().iv());
|
|
keys[0].set_track_label(keyc.track_label());
|
|
//TODO: passing empty cipher_mode and srm_req params - OK?
|
|
CdmResponseType result = crypto_session_->LoadKeys(
|
|
sm.msg(), sm.signature(), std::string(), std::string(), keys,
|
|
std::string(), std::string());
|
|
if (result != KEY_ADDED) {
|
|
LOGE("CdmLicense::HandleSubLicense: LoadKeys() call failed, result=%d",
|
|
result);
|
|
return result;
|
|
}
|
|
loaded_keys.insert(keyc.id());
|
|
*license.add_key() = keyc;
|
|
}
|
|
loaded_keys_.swap(loaded_keys);
|
|
policy_engine_->UpdateLicenseKeys(license);
|
|
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
bool CdmLicense::RestoreOfflineLicense(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response,
|
|
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
|
|
int64_t last_playback_time, int64_t grace_period_end_time,
|
|
CdmSession* cdm_session) {
|
|
if (license_request.empty() || license_response.empty()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreOfflineLicense: key_request or response empty: "
|
|
"%u %u",
|
|
license_request.size(), license_response.size());
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("CdmLicense::RestoreOfflineLicense: license_request parse failed");
|
|
return false;
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE(
|
|
"CdmLicense::RestoreOfflineLicense: license request type: expected = "
|
|
"%d, actual = %d",
|
|
SignedMessage::LICENSE_REQUEST, signed_request.type());
|
|
return false;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
key_request_ = signed_request.msg();
|
|
} else {
|
|
if (!crypto_session_->GenerateDerivedKeys(signed_request.msg())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CdmResponseType sts = HandleKeyResponse(license_response);
|
|
|
|
if (sts != KEY_ADDED) return false;
|
|
|
|
if (!license_renewal_response.empty()) {
|
|
sts = HandleKeyUpdateResponse(true, license_renewal_response);
|
|
|
|
if (sts != KEY_ADDED) return false;
|
|
}
|
|
|
|
if (!provider_session_token_.empty()) {
|
|
if (cdm_session &&
|
|
cdm_session->get_usage_support_type() == kUsageEntrySupport) {
|
|
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
|
if (NO_ERROR != status) return false;
|
|
}
|
|
|
|
std::string usage_report;
|
|
CryptoSession::UsageDurationStatus usage_duration_status =
|
|
CryptoSession::kUsageDurationsInvalid;
|
|
|
|
int64_t seconds_since_started, seconds_since_last_played;
|
|
sts = crypto_session_->GenerateUsageReport(
|
|
provider_session_token_, &usage_report, &usage_duration_status,
|
|
&seconds_since_started, &seconds_since_last_played);
|
|
|
|
if (NO_ERROR == sts) {
|
|
switch (usage_duration_status) {
|
|
case CryptoSession::kUsageDurationPlaybackNotBegun:
|
|
playback_start_time = 0;
|
|
last_playback_time = 0;
|
|
break;
|
|
case CryptoSession::kUsageDurationsValid: {
|
|
int64_t current_time = clock_->GetCurrentTime();
|
|
if (current_time - seconds_since_started > 0)
|
|
playback_start_time = current_time - seconds_since_started;
|
|
if (current_time - last_playback_time > 0)
|
|
last_playback_time = current_time - seconds_since_last_played;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
policy_engine_->RestorePlaybackTimes(playback_start_time, last_playback_time,
|
|
grace_period_end_time);
|
|
return true;
|
|
}
|
|
|
|
bool CdmLicense::RestoreLicenseForRelease(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response) {
|
|
if (license_request.empty() || license_response.empty()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: key_request or response empty:"
|
|
" %u %u",
|
|
license_request.size(), license_response.size());
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("CdmLicense::RestoreLicenseForRelease: license_request parse failed");
|
|
return false;
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: license request type: expected "
|
|
"= %d, actual = %d",
|
|
SignedMessage::LICENSE_REQUEST, signed_request.type());
|
|
return false;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
key_request_ = signed_request.msg();
|
|
} else {
|
|
if (!crypto_session_->GenerateDerivedKeys(signed_request.msg())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unable to parse signed license"
|
|
" response");
|
|
return false;
|
|
}
|
|
|
|
if (SignedMessage::LICENSE != signed_response.type()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unrecognized signed message "
|
|
"type: %d",
|
|
signed_response.type());
|
|
return false;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: license response is not"
|
|
" signed");
|
|
return false;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unable to parse license"
|
|
" response");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token())
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
|
|
if (license.policy().has_always_include_client_id())
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("CdmLicense::RestoreLicenseForRelease: no session keys present");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token()) {
|
|
if (!crypto_session_->GenerateDerivedKeys(key_request_,
|
|
signed_response.session_key()))
|
|
return false;
|
|
} else {
|
|
return KEY_ADDED == HandleKeyResponse(license_response);
|
|
}
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url())
|
|
server_url_ = license.policy().renewal_server_url();
|
|
|
|
// If the policy engine already has keys, they will now expire.
|
|
// If the policy engine does not already have keys, this will not add any.
|
|
policy_engine_->SetLicenseForRelease(license);
|
|
return true;
|
|
}
|
|
|
|
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
|
|
return loaded_keys_.find(key_id) != loaded_keys_.end();
|
|
}
|
|
|
|
bool CdmLicense::ExtractProviderSessionToken(
|
|
const CdmKeyResponse& license_response,
|
|
std::string* provider_session_token) {
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::ExtractProviderSessionToken: empty license response");
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE(
|
|
"CdmLicense::ExtractProviderSessionToken: unable to parse signed "
|
|
"license response");
|
|
return false;
|
|
}
|
|
|
|
if (signed_response.type() != SignedMessage::LICENSE) {
|
|
LOGE("CdmLicense::ExtractProviderSessionToken: unrecognized signed message "
|
|
"type: %d", signed_response.type());
|
|
return false;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("CdmLicense::ExtractProviderSessionToken: unable to parse license "
|
|
"response");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token() &&
|
|
!license.id().provider_session_token().empty()) {
|
|
*provider_session_token = license.id().provider_session_token();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
|
const SignedMessage& signed_message) {
|
|
LicenseError license_error;
|
|
if (!license_error.ParseFromString(signed_message.msg())) {
|
|
LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error");
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
switch (license_error.error_code()) {
|
|
case LicenseError::INVALID_DRM_DEVICE_CERTIFICATE:
|
|
return NEED_PROVISIONING;
|
|
case LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE:
|
|
return DEVICE_REVOKED;
|
|
case LicenseError::SERVICE_UNAVAILABLE:
|
|
default:
|
|
LOGW("CdmLicense::HandleKeyErrorResponse: Unknown error type = %d",
|
|
license_error.error_code());
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
// Return the ClientIdentification message token type for license request.
|
|
// NOTE: an OEM Cert should never be presented to the provisioning server.
|
|
bool CdmLicense::GetClientTokenType(
|
|
ClientIdentification::TokenType* token_type) {
|
|
switch (client_token_type_) {
|
|
case kClientTokenKeybox:
|
|
*token_type = ClientIdentification::KEYBOX;
|
|
return true;
|
|
case kClientTokenDrmCert:
|
|
*token_type = ClientIdentification::DRM_DEVICE_CERTIFICATE;
|
|
return true;
|
|
case kClientTokenOemCert:
|
|
default:
|
|
// shouldn't happen
|
|
LOGE("GetClientTokenType: BAD TOKEN TYPE");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareClientId(
|
|
const CdmAppParameterMap& app_parameters, LicenseRequest* license_request) {
|
|
ClientIdentification* client_id = license_request->mutable_client_id();
|
|
|
|
ClientIdentification::TokenType token_type;
|
|
if (!GetClientTokenType(&token_type)) {
|
|
return LICENSING_CLIENT_TOKEN_ERROR_1;
|
|
}
|
|
client_id->set_type(token_type);
|
|
client_id->set_token(client_token_);
|
|
|
|
ClientIdentification_NameValue* client_info;
|
|
CdmAppParameterMap::const_iterator iter;
|
|
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(iter->first);
|
|
client_info->set_value(iter->second);
|
|
}
|
|
std::string value;
|
|
if (Properties::GetCompanyName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kCompanyNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetModelName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kModelNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetArchitectureName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kArchitectureNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetDeviceName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetProductName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kProductNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetBuildInfo(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kBuildInfoKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (!device_id_.empty()) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceIdKey);
|
|
client_info->set_value(b2a_hex(device_id_));
|
|
} else if (crypto_session_->GetInternalDeviceUniqueId(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceIdKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetWVCdmVersion(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
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)crypto_session_->GetSecurityPatchLevel();
|
|
client_info->set_value(ss.str());
|
|
|
|
ClientIdentification_ClientCapabilities* client_capabilities =
|
|
client_id->mutable_client_capabilities();
|
|
bool supports_usage_information;
|
|
if (crypto_session_->UsageInformationSupport(&supports_usage_information)) {
|
|
client_capabilities->set_session_token(supports_usage_information);
|
|
}
|
|
|
|
client_capabilities->set_anti_rollback_usage_table(
|
|
crypto_session_->IsAntiRollbackHwPresent());
|
|
|
|
uint32_t api_version = 0;
|
|
if (crypto_session_->GetApiVersion(&api_version)) {
|
|
client_capabilities->set_oem_crypto_api_version(api_version);
|
|
}
|
|
|
|
CryptoSession::HdcpCapability current_version, max_version;
|
|
if (crypto_session_->GetHdcpCapabilities(¤t_version, &max_version)) {
|
|
switch (max_version) {
|
|
case HDCP_NONE:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE);
|
|
break;
|
|
case HDCP_V1:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1);
|
|
break;
|
|
case HDCP_V2:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2);
|
|
break;
|
|
case HDCP_V2_1:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1);
|
|
break;
|
|
case HDCP_V2_2:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2);
|
|
break;
|
|
case HDCP_NO_DIGITAL_OUTPUT:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NO_DIGITAL_OUTPUT);
|
|
break;
|
|
default:
|
|
LOGW(
|
|
"CdmLicense::PrepareClientId: unexpected HDCP max capability "
|
|
"version %d",
|
|
max_version);
|
|
}
|
|
}
|
|
|
|
CryptoSession::SupportedCertificateTypes supported_certs;
|
|
if (crypto_session_->GetSupportedCertificateTypes(&supported_certs)) {
|
|
if (supported_certs.rsa_2048_bit) {
|
|
client_capabilities->add_supported_certificate_key_type(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048);
|
|
}
|
|
if (supported_certs.rsa_3072_bit) {
|
|
client_capabilities->add_supported_certificate_key_type(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072);
|
|
}
|
|
}
|
|
|
|
client_capabilities->set_can_update_srm(
|
|
crypto_session_->IsSrmUpdateSupported());
|
|
uint16_t srm_version;
|
|
if (crypto_session_->GetSrmVersion(&srm_version))
|
|
client_capabilities->set_srm_version(srm_version);
|
|
|
|
if (Properties::UsePrivacyMode(session_id_)) {
|
|
if (service_certificate_->certificate().empty()) {
|
|
LOGE("CdmLicense::PrepareClientId: Service Certificate not staged");
|
|
return PRIVACY_MODE_ERROR_3;
|
|
}
|
|
EncryptedClientIdentification* encrypted_client_id =
|
|
license_request->mutable_encrypted_client_id();
|
|
CdmResponseType status;
|
|
status = service_certificate_->EncryptClientId(crypto_session_, client_id,
|
|
encrypted_client_id);
|
|
if (NO_ERROR == status) {
|
|
license_request->clear_client_id();
|
|
} else {
|
|
license_request->clear_encrypted_client_id();
|
|
}
|
|
return status;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareContentId(
|
|
const InitializationData& init_data, CdmLicenseType license_type,
|
|
const std::string& request_id, LicenseRequest* license_request) {
|
|
// Content Identification may be a cenc_id, a webm_id or a license_id
|
|
LicenseRequest_ContentIdentification* content_id =
|
|
license_request->mutable_content_id();
|
|
|
|
if (init_data.is_cenc() || init_data.is_hls()) {
|
|
LicenseRequest_ContentIdentification_CencDeprecated* cenc_content_id =
|
|
content_id->mutable_cenc_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
cenc_content_id->add_pssh(init_data.data());
|
|
} else {
|
|
LOGE("CdmLicense::PrepareContentId: ISO-CENC init data not available");
|
|
return CENC_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, cenc_content_id)) {
|
|
return PREPARE_CENC_CONTENT_ID_FAILED;
|
|
}
|
|
} else if (init_data.is_webm()) {
|
|
LicenseRequest_ContentIdentification_WebmDeprecated* webm_content_id =
|
|
content_id->mutable_webm_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
webm_content_id->set_header(init_data.data());
|
|
} else {
|
|
LOGE("CdmLicense::PrepareContentId: WebM init data not available");
|
|
return WEBM_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, webm_content_id)) {
|
|
return PREPARE_WEBM_CONTENT_ID_FAILED;
|
|
}
|
|
} else {
|
|
LOGE("CdmLicense::PrepareContentId: no support for init data type (%s)",
|
|
init_data.type().c_str());
|
|
return UNSUPPORTED_INIT_DATA_FORMAT;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
template <typename T>
|
|
bool CdmLicense::SetTypeAndId(CdmLicenseType license_type,
|
|
const std::string& request_id, T* content_id) {
|
|
switch (license_type) {
|
|
case kLicenseTypeOffline:
|
|
content_id->set_license_type(video_widevine::OFFLINE);
|
|
break;
|
|
case kLicenseTypeStreaming:
|
|
case kLicenseTypeTemporary:
|
|
content_id->set_license_type(video_widevine::STREAMING);
|
|
break;
|
|
default:
|
|
LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d",
|
|
license_type);
|
|
return false;
|
|
}
|
|
|
|
content_id->set_request_id(request_id);
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvcdm
|