[ Merge of http://go/wvgerrit/189590 ] [ Cherry-pick of http://ag/26541307 ] The CDM session shares its CryptoSession instance with a few additional member objects (CdmLicense and PolicyEngine). When the CDM session's crypto session is reset, it must also reset the CdmLicense and PolicyEngine otherwise, a potential stale pointer reference may occur. Test: request_license_test on Oriole Test: run_x86_64_tests Bug: 311239278 Change-Id: Ie175513ae652dcd96e12e5e1def574a8a56d5863
1207 lines
42 KiB
C++
1207 lines
42 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine License
|
|
// Agreement.
|
|
|
|
#include "license.h"
|
|
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "cdm_session.h"
|
|
#include "client_identification.h"
|
|
#include "clock.h"
|
|
#include "crypto_key.h"
|
|
#include "crypto_session.h"
|
|
#include "device_files.h"
|
|
#include "license_protocol_conversions.h"
|
|
#include "log.h"
|
|
#include "platform.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 uint32_t kFourCcCbc1 = 0x63626331;
|
|
const uint32_t kFourCcCbcs = 0x63626373;
|
|
const uint32_t kFourCcLittleEndianCbc1 = 0x31636263;
|
|
const uint32_t kFourCcLittleEndianCbcs = 0x73636263;
|
|
const uint32_t kFourCcCenc = 0x63656e63;
|
|
|
|
const std::string kEmptyString;
|
|
|
|
// MAC key in the license are two separate MAC keys (client and server).
|
|
constexpr size_t kLicenseMacKeySize = wvcdm::MAC_KEY_SIZE * 2;
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
// Protobuf generated classes.
|
|
using video_widevine::EncryptedClientIdentification;
|
|
using video_widevine::HashAlgorithmProto;
|
|
using video_widevine::License;
|
|
using video_widevine::License_KeyContainer;
|
|
using video_widevine::LicenseError;
|
|
using video_widevine::LicenseIdentification;
|
|
using video_widevine::LicenseRequest;
|
|
using video_widevine::LicenseRequest_ContentIdentification;
|
|
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
|
|
using video_widevine::LicenseRequest_ContentIdentification_WebmKeyId;
|
|
using video_widevine::LicenseRequest_ContentIdentification_WidevinePsshData;
|
|
using video_widevine::SignedMessage;
|
|
|
|
namespace {
|
|
std::vector<CryptoKey> ExtractEntitlementKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::ENTITLEMENT: {
|
|
// We always take the first ENTITLEMENT_KEY_SIZE bytes and ignore the
|
|
// rest in order to ignore any padding.
|
|
//
|
|
// TODO(b/232464183): When we switch to License Protocol 2.2, there will
|
|
// no longer be padding on these keys, so this
|
|
// removal code can be removed at the same time.
|
|
if (license.key(i).key().size() < ENTITLEMENT_KEY_SIZE) {
|
|
LOGE(
|
|
"Skipping key %s because it is too small. Expected: %zu vs. "
|
|
"Actual: %zu",
|
|
license.key(i).id().c_str(), ENTITLEMENT_KEY_SIZE,
|
|
license.key(i).key().size());
|
|
continue;
|
|
}
|
|
key.set_key_data(license.key(i).key().substr(0, ENTITLEMENT_KEY_SIZE));
|
|
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());
|
|
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;
|
|
|
|
default:
|
|
// Ignore all but ENTITLEMENT key types.
|
|
break;
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
std::vector<CryptoKey> ExtractContentKeys(
|
|
const License& license, video_widevine::ProtocolVersion version) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
// Extract content key(s)
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::CONTENT:
|
|
case License_KeyContainer::OPERATOR_SESSION: {
|
|
key.set_key_id(license.key(i).id());
|
|
// KeyContainers have a fixed 16 bytes of padding in License Protocol
|
|
// 2.1. Note that OPERATION_SESSION keys may be CONTENT_KEY_SIZE or
|
|
// MAC_KEY_SIZE, so we cannot assume the key size here.
|
|
//
|
|
// TODO(b/232464183): When we switch to License Protocol 2.2, there will
|
|
// no longer be padding on these keys, so this
|
|
// removal code must be removed at the same time.
|
|
const auto padding = version <= video_widevine::VERSION_2_1
|
|
? LICENSE_PROTOCOL_2_1_PADDING
|
|
: 0;
|
|
if (license.key(i).key().size() != CONTENT_KEY_SIZE + padding &&
|
|
license.key(i).key().size() != MAC_KEY_SIZE + padding) {
|
|
LOGE(
|
|
"Skipping key %s because it is an unexpected size. Expected: %zu "
|
|
"or %zu, Actual: %zu",
|
|
license.key(i).id().c_str(), CONTENT_KEY_SIZE + padding,
|
|
MAC_KEY_SIZE + padding, license.key(i).key().size());
|
|
continue;
|
|
}
|
|
const size_t length =
|
|
license.key(i).key().size() - padding;
|
|
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;
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
} // namespace
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
|
: session_id_(session_id),
|
|
initialized_(false),
|
|
protocol_version_(video_widevine::VERSION_2_2),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false),
|
|
use_privacy_mode_(false),
|
|
clock_(new wvutil::Clock()),
|
|
license_key_type_(kLicenseKeyTypeContent) {}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock)
|
|
: session_id_(session_id),
|
|
initialized_(false),
|
|
protocol_version_(video_widevine::VERSION_2_2),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false),
|
|
use_privacy_mode_(false),
|
|
clock_(clock),
|
|
license_key_type_(kLicenseKeyTypeContent) {
|
|
if (!clock_) {
|
|
LOGW("Input |clock| is null, using default");
|
|
clock_.reset(new wvutil::Clock());
|
|
}
|
|
}
|
|
|
|
CdmLicense::~CdmLicense() {}
|
|
|
|
bool CdmLicense::Init(bool use_privacy_mode,
|
|
const std::string& signed_service_certificate,
|
|
CryptoSession* session, PolicyEngine* policy_engine) {
|
|
if (!clock_) {
|
|
LOGE("Clock parameter not provided");
|
|
return false;
|
|
}
|
|
if (session_id_.empty()) {
|
|
LOGE("Session ID not provided");
|
|
return false;
|
|
}
|
|
if (session == nullptr || !session->IsOpen()) {
|
|
LOGE("Crypto session not provided or not open");
|
|
return false;
|
|
}
|
|
if (policy_engine == nullptr) {
|
|
LOGE("Policy engine not provided");
|
|
return false;
|
|
}
|
|
|
|
if (use_privacy_mode && !signed_service_certificate.empty() &&
|
|
service_certificate_.Init(signed_service_certificate) != NO_ERROR) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t api_version;
|
|
if (!session->GetApiVersion(&api_version)) {
|
|
api_version = 16;
|
|
}
|
|
protocol_version_ = api_version >= 19 ? video_widevine::VERSION_2_2
|
|
: video_widevine::VERSION_2_1;
|
|
|
|
crypto_session_ = session;
|
|
policy_engine_ = policy_engine;
|
|
use_privacy_mode_ = use_privacy_mode;
|
|
license_nonce_ = 0;
|
|
initialized_ = true;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::SetServiceCertificate(
|
|
const std::string& signed_service_certificate) {
|
|
return service_certificate_.Init(signed_service_certificate);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyRequest(
|
|
const InitializationData& init_data, const std::string& client_token,
|
|
CdmLicenseType license_type, const CdmAppParameterMap& app_parameters,
|
|
CdmKeyMessage* signed_request, std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return CdmResponseType(LICENSE_PARSER_NOT_INITIALIZED_4);
|
|
}
|
|
client_token_ = client_token;
|
|
if (init_data.IsEmpty() && stored_init_data_) {
|
|
InitializationData restored_init_data = *stored_init_data_;
|
|
stored_init_data_.reset();
|
|
return PrepareKeyRequest(restored_init_data, client_token, license_type,
|
|
app_parameters, signed_request, server_url);
|
|
}
|
|
wrapped_keys_ = init_data.ExtractWrappedKeys();
|
|
if (!init_data.is_supported()) {
|
|
LOGE("Unsupported init data type: type = %s", init_data.type().c_str());
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_3);
|
|
}
|
|
if (init_data.IsEmpty()) {
|
|
LOGE("Init data is empty");
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_4);
|
|
}
|
|
if (signed_request == nullptr) {
|
|
LOGE("Output parameter |signed_request| not provided");
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_6);
|
|
}
|
|
if (server_url == nullptr) {
|
|
LOGE("Output parameter |server_url| not provided");
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_7);
|
|
}
|
|
|
|
// If privacy mode and no service certificate, depending on platform
|
|
// configuration, request service certificate or declare error
|
|
if (use_privacy_mode_ && !service_certificate_.has_certificate()) {
|
|
if (!Properties::allow_service_certificate_requests()) {
|
|
LOGE("Privacy mode failure: No service certificate");
|
|
return CdmResponseType(PRIVACY_MODE_ERROR_1);
|
|
}
|
|
|
|
stored_init_data_.reset(new InitializationData(init_data));
|
|
|
|
if (!ServiceCertificate::GetRequest(signed_request)) {
|
|
LOGE("Failed to prepare service certificated request");
|
|
return CdmResponseType(
|
|
LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR);
|
|
}
|
|
|
|
return CdmResponseType(KEY_MESSAGE);
|
|
}
|
|
|
|
const std::string& request_id = crypto_session_->request_id();
|
|
|
|
LicenseRequest license_request;
|
|
CdmResponseType status;
|
|
status = PrepareClientId(app_parameters, kEmptyString, &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.
|
|
status = crypto_session_->GenerateNonce(&license_nonce_);
|
|
|
|
switch (status.code()) {
|
|
case NO_ERROR:
|
|
break;
|
|
case SESSION_LOST_STATE_ERROR:
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
return status;
|
|
default:
|
|
return CdmResponseType(LICENSE_REQUEST_NONCE_GENERATION_ERROR);
|
|
}
|
|
license_request.set_key_control_nonce(license_nonce_);
|
|
license_request.set_protocol_version(protocol_version_);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
key_request_ = serialized_license_req;
|
|
|
|
// Derive signing and encryption keys and construct core message and
|
|
// signature.
|
|
std::string core_message;
|
|
std::string license_request_signature;
|
|
bool should_specify_algorithm;
|
|
OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1;
|
|
status = crypto_session_->PrepareAndSignLicenseRequest(
|
|
serialized_license_req, &core_message, &license_request_signature,
|
|
should_specify_algorithm, oec_algorithm);
|
|
|
|
if (status != NO_ERROR) {
|
|
signed_request->clear();
|
|
return status;
|
|
}
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("License request signature is empty");
|
|
signed_request->clear();
|
|
return CdmResponseType(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.set_oemcrypto_core_message(core_message);
|
|
if (should_specify_algorithm) {
|
|
HashAlgorithmProto proto_algorithm =
|
|
HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED;
|
|
if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) {
|
|
return CdmResponseType(UNSUPPORTED_SIGNATURE_HASH_ALGORITHM_1);
|
|
}
|
|
signed_message.set_hash_algorithm(proto_algorithm);
|
|
}
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
|
|
*server_url = server_url_;
|
|
return CdmResponseType(KEY_MESSAGE);
|
|
}
|
|
|
|
// TODO(b/166007195): Remove this.
|
|
CdmResponseType CdmLicense::PrepareKeyUpdateReload(CdmSession* cdm_session) {
|
|
uint32_t api_version = 0;
|
|
if (!crypto_session_->GetApiVersion(&api_version)) {
|
|
LOGW("Unknown API Version");
|
|
api_version = 16;
|
|
}
|
|
if (api_version != 16) return CdmResponseType(NO_ERROR);
|
|
// To work around b/166010609, we ask OEMCrypto to prepare an unused renewal
|
|
// request. This lets the ODK library update its clock saying when the renewal
|
|
// was signed.
|
|
constexpr bool is_renewal = true;
|
|
const CdmAppParameterMap unused_app_parameters;
|
|
CdmKeyMessage unused_request;
|
|
std::string unused_url;
|
|
return PrepareKeyUpdateRequest(is_renewal, unused_app_parameters, cdm_session,
|
|
&unused_request, &unused_url);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
|
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
|
CdmSession* cdm_session, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return CdmResponseType(LICENSE_PARSER_NOT_INITIALIZED_1);
|
|
}
|
|
if (signed_request == nullptr) {
|
|
LOGE("Output parameter |signed_request| not provided");
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_1);
|
|
}
|
|
if (server_url == nullptr) {
|
|
LOGE("Output parameter |server_url| not provided");
|
|
return CdmResponseType(INVALID_PARAMETERS_LIC_2);
|
|
}
|
|
|
|
if (is_renewal && !policy_engine_->CanRenew()) {
|
|
LOGE("License renewal prohibited");
|
|
return CdmResponseType(LICENSE_RENEWAL_PROHIBITED);
|
|
}
|
|
|
|
if (renew_with_client_id_) {
|
|
if (use_privacy_mode_ && !service_certificate_.has_certificate()) {
|
|
LOGE("Privacy mode failure: No service certificate");
|
|
return CdmResponseType(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, provider_client_token_, &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->SupportsUsageTable()) {
|
|
const 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 CdmResponseType(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);
|
|
}
|
|
|
|
license_request.set_protocol_version(protocol_version_);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
// Construct signature and core message.
|
|
std::string core_message;
|
|
std::string license_request_signature;
|
|
const CdmResponseType status = crypto_session_->PrepareAndSignRenewalRequest(
|
|
serialized_license_req, &core_message, &license_request_signature);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("License request signature is empty");
|
|
return CdmResponseType(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.set_oemcrypto_core_message(core_message);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
*server_url = server_url_;
|
|
return CdmResponseType(KEY_MESSAGE);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyResponse(
|
|
bool is_restore, const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return CdmResponseType(LICENSE_PARSER_NOT_INITIALIZED_2);
|
|
}
|
|
// Clear the latest service version when we receive a new response.
|
|
latest_service_version_.Clear();
|
|
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_RESPONSE_1);
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Unable to parse signed license response");
|
|
return CdmResponseType(INVALID_LICENSE_RESPONSE);
|
|
}
|
|
|
|
latest_service_version_ = signed_response.service_version_info();
|
|
|
|
if (use_privacy_mode_ && Properties::allow_service_certificate_requests() &&
|
|
signed_response.type() == SignedMessage::SERVICE_CERTIFICATE) {
|
|
std::string signed_certificate;
|
|
CdmResponseType status = ServiceCertificate::ParseResponse(
|
|
license_response, &signed_certificate);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
status = service_certificate_.Init(signed_certificate);
|
|
return (status == NO_ERROR) ? CdmResponseType(NEED_KEY) : status;
|
|
}
|
|
|
|
if (signed_response.type() == SignedMessage::ERROR_RESPONSE)
|
|
return HandleKeyErrorResponse(signed_response);
|
|
|
|
if (signed_response.type() != SignedMessage::LICENSE) {
|
|
LOGE("Unrecognized signed message type: type = %d",
|
|
static_cast<int>(signed_response.type()));
|
|
return CdmResponseType(INVALID_LICENSE_TYPE);
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("License response is not signed");
|
|
return CdmResponseType(LICENSE_RESPONSE_NOT_SIGNED);
|
|
}
|
|
|
|
const std::string& signed_message = signed_response.msg();
|
|
const std::string& core_message = signed_response.oemcrypto_core_message();
|
|
const std::string& signature = signed_response.signature();
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_message)) {
|
|
LOGE("Unable to parse license response");
|
|
return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_1);
|
|
}
|
|
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("Signed response has no session keys present");
|
|
return CdmResponseType(SESSION_KEYS_NOT_FOUND);
|
|
}
|
|
|
|
// Extract mac key
|
|
std::string mac_key_iv;
|
|
std::string mac_keys;
|
|
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_keys.assign(
|
|
license.key(i).key().data(),
|
|
std::min(kLicenseMacKeySize, license.key(i).key().size()));
|
|
}
|
|
}
|
|
if (license.policy().can_renew() ||
|
|
(!mac_key_iv.empty() || !mac_keys.empty())) {
|
|
if (mac_key_iv.size() != KEY_IV_SIZE ||
|
|
mac_keys.size() != kLicenseMacKeySize) {
|
|
LOGE(
|
|
"MAC key/IV size error: expected = %zu/%zu, "
|
|
"actual = %zu/%zu (key/iv)",
|
|
kLicenseMacKeySize, KEY_IV_SIZE, mac_keys.size(), mac_key_iv.size());
|
|
return CdmResponseType(KEY_SIZE_ERROR_1);
|
|
}
|
|
}
|
|
|
|
// A license may contain several types of keys (content, entitlement,
|
|
// signing, key control and operator sessions); however, it should not
|
|
// contain both entitlement keys and content keys. To determine the
|
|
// overall type of the license, we check for the existence of either
|
|
// type of keys. If both are present, we default to entitlement keys.
|
|
CdmLicenseKeyType key_type = kLicenseKeyTypeEntitlement;
|
|
std::vector<CryptoKey> key_array = ExtractEntitlementKeys(license);
|
|
if (key_array.empty()) {
|
|
key_array = ExtractContentKeys(license, protocol_version_);
|
|
key_type = kLicenseKeyTypeContent;
|
|
}
|
|
if (key_array.empty()) {
|
|
LOGE("No content keys");
|
|
return CdmResponseType(NO_CONTENT_KEY);
|
|
}
|
|
license_key_type_ = key_type;
|
|
|
|
if (license.has_provider_client_token())
|
|
provider_client_token_ = license.provider_client_token();
|
|
|
|
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();
|
|
}
|
|
|
|
// If the field is not set, it will default to false.
|
|
CdmResponseType status =
|
|
crypto_session_->UseSecondaryKey(signed_response.using_secondary_key());
|
|
if (status != NO_ERROR) return status;
|
|
|
|
CdmResponseType resp(NO_CONTENT_KEY);
|
|
if (kLicenseKeyTypeEntitlement == key_type) {
|
|
resp = HandleEntitlementKeyResponse(
|
|
is_restore, signed_response.session_key(), signed_message, core_message,
|
|
signature, key_array, license);
|
|
} else if (kLicenseKeyTypeContent == key_type) {
|
|
resp = HandleContentKeyResponse(is_restore, signed_response.session_key(),
|
|
signed_message, core_message, signature,
|
|
key_array, license);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
|
bool is_renewal, bool is_restore, const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return CdmResponseType(LICENSE_PARSER_NOT_INITIALIZED_3);
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_RESPONSE_2);
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Unable to parse signed message");
|
|
return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_2);
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE("Unrecognized signed message type: type = %d",
|
|
static_cast<int>(signed_response.type()));
|
|
return CdmResponseType(INVALID_LICENSE_TYPE);
|
|
}
|
|
|
|
const std::string& signed_message = signed_response.msg();
|
|
const std::string& core_message = signed_response.oemcrypto_core_message();
|
|
const std::string& signature = signed_response.signature();
|
|
|
|
if (is_renewal && core_message.empty()) {
|
|
LOGE("Renewal response is missing |core_message| field");
|
|
return CdmResponseType(CORE_MESSAGE_NOT_FOUND);
|
|
}
|
|
|
|
if (signature.empty()) {
|
|
LOGE("Update key response is missing signature");
|
|
return CdmResponseType(SIGNATURE_NOT_FOUND);
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_message)) {
|
|
LOGE("Unable to parse license from signed message");
|
|
return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_3);
|
|
}
|
|
|
|
if (!license.has_id()) {
|
|
LOGE("License ID not present");
|
|
return CdmResponseType(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 CdmResponseType(KEY_ADDED);
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url() &&
|
|
license.policy().renewal_server_url().size() > 0) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
// If the field is not set, it will default to false.
|
|
CdmResponseType status =
|
|
crypto_session_->UseSecondaryKey(signed_response.using_secondary_key());
|
|
if (status != NO_ERROR) return status;
|
|
|
|
status =
|
|
crypto_session_->LoadRenewal(signed_message, core_message, signature);
|
|
|
|
if (status == KEY_ADDED) {
|
|
policy_engine_->UpdateLicense(license, is_restore);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleEmbeddedKeyData(
|
|
const InitializationData& init_data) {
|
|
return HandleNewEntitledKeys(init_data.ExtractWrappedKeys());
|
|
}
|
|
|
|
CdmResponseType CdmLicense::RestoreOfflineLicense(
|
|
const std::string& client_token, 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()) {
|
|
LOGE("License request is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_REQUEST_2);
|
|
}
|
|
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_RESPONSE_3);
|
|
}
|
|
|
|
client_token_ = client_token;
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("Failed to parse license request");
|
|
return CdmResponseType(PARSE_REQUEST_ERROR_1);
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE("Unexpected license request type: expected = %d, actual = %d",
|
|
static_cast<int>(SignedMessage::LICENSE_REQUEST),
|
|
static_cast<int>(signed_request.type()));
|
|
return CdmResponseType(INVALID_LICENSE_REQUEST_TYPE_1);
|
|
}
|
|
|
|
key_request_ = signed_request.msg();
|
|
LicenseRequest original_license_request;
|
|
if (!original_license_request.ParseFromString(key_request_)) {
|
|
LOGW("Could not parse original request.");
|
|
} else {
|
|
license_nonce_ = original_license_request.key_control_nonce();
|
|
protocol_version_ = original_license_request.protocol_version();
|
|
}
|
|
|
|
CdmResponseType sts = HandleKeyResponse(true, license_response);
|
|
|
|
if (sts != KEY_ADDED) return sts;
|
|
|
|
if (!license_renewal_response.empty()) {
|
|
sts = PrepareKeyUpdateReload(cdm_session);
|
|
if (sts != KEY_MESSAGE && sts != NO_ERROR) return sts;
|
|
sts = HandleKeyUpdateResponse(true, true, license_renewal_response);
|
|
if (sts != KEY_ADDED) return sts;
|
|
}
|
|
|
|
if (!provider_session_token_.empty()) {
|
|
if (cdm_session && cdm_session->SupportsUsageTable()) {
|
|
const CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
|
if (NO_ERROR != status) return sts;
|
|
}
|
|
|
|
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();
|
|
playback_start_time = current_time - seconds_since_started;
|
|
last_playback_time = current_time - seconds_since_last_played;
|
|
break;
|
|
}
|
|
default:
|
|
// Use playback_start_time and last_playback_time from
|
|
// persistently stored license data
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
policy_engine_->RestorePlaybackTimes(playback_start_time, last_playback_time,
|
|
grace_period_end_time);
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::RestoreLicenseForRelease(
|
|
const std::string& client_token, const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response) {
|
|
if (license_request.empty()) {
|
|
LOGE("License request is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_REQUEST_3);
|
|
}
|
|
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return CdmResponseType(EMPTY_LICENSE_RESPONSE_4);
|
|
}
|
|
|
|
client_token_ = client_token;
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("Failed to parse signed license request");
|
|
return CdmResponseType(PARSE_REQUEST_ERROR_2);
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE("Unexpected signed license request type: expected = %d, actual = %d",
|
|
static_cast<int>(SignedMessage::LICENSE_REQUEST),
|
|
static_cast<int>(signed_request.type()));
|
|
return CdmResponseType(INVALID_LICENSE_REQUEST_TYPE_2);
|
|
}
|
|
|
|
key_request_ = signed_request.msg();
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Failed to parse signed license response");
|
|
return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_4);
|
|
}
|
|
|
|
if (SignedMessage::LICENSE != signed_response.type()) {
|
|
LOGE("Unexpected signed license response type: expected = %d, actual = %d",
|
|
static_cast<int>(SignedMessage::LICENSE),
|
|
static_cast<int>(signed_response.type()));
|
|
return CdmResponseType(INVALID_LICENSE_TYPE_2);
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("License response is not signed");
|
|
return CdmResponseType(SIGNATURE_NOT_FOUND_2);
|
|
}
|
|
|
|
if (!signed_response.has_oemcrypto_core_message()) {
|
|
LOGE("License response is missing core message");
|
|
return CdmResponseType(CORE_MESSAGE_NOT_FOUND);
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("Failed to parse license response");
|
|
return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_5);
|
|
}
|
|
|
|
if (license.has_provider_client_token())
|
|
provider_client_token_ = license.provider_client_token();
|
|
|
|
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 (!signed_response.has_session_key()) {
|
|
LOGE("No session keys present");
|
|
return CdmResponseType(SESSION_KEYS_NOT_FOUND_2);
|
|
}
|
|
|
|
if (!license.id().has_provider_session_token()) {
|
|
CdmResponseType result = HandleKeyResponse(false, license_response);
|
|
return result == KEY_ADDED ? CdmResponseType(NO_ERROR) : result;
|
|
}
|
|
|
|
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 CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
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("License response is empty");
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Failed to parse signed license response");
|
|
return false;
|
|
}
|
|
|
|
if (signed_response.type() != SignedMessage::LICENSE) {
|
|
LOGE("Unexpected signed license response type: expected = %d, actual = %d",
|
|
static_cast<int>(SignedMessage::LICENSE),
|
|
static_cast<int>(signed_response.type()));
|
|
return false;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("Failed 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("Failed to parse license error response");
|
|
return CdmResponseType(KEY_ERROR);
|
|
}
|
|
|
|
// TODO(b/261185349) Add new field in CdmResponseType to handle license_error
|
|
switch (license_error.error_code()) {
|
|
case LicenseError::INVALID_DRM_DEVICE_CERTIFICATE:
|
|
return CdmResponseType(NEED_PROVISIONING);
|
|
case LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE:
|
|
return CdmResponseType(DEVICE_REVOKED);
|
|
case LicenseError::SERVICE_UNAVAILABLE:
|
|
default:
|
|
LOGW("Unknown error type: error_code = %d",
|
|
static_cast<int>(license_error.error_code()));
|
|
return CdmResponseType(KEY_ERROR);
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareClientId(
|
|
const CdmAppParameterMap& app_parameters,
|
|
const std::string& provider_client_token, LicenseRequest* license_request) {
|
|
wvcdm::ClientIdentification id;
|
|
if (client_token_.empty()) {
|
|
LOGE("Client token not set when preparing client ID");
|
|
return CdmResponseType(CLIENT_TOKEN_NOT_SET);
|
|
}
|
|
|
|
CdmResponseType status =
|
|
id.InitForLicenseRequest(client_token_, crypto_session_);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
video_widevine::ClientIdentification* client_id =
|
|
license_request->mutable_client_id();
|
|
status = id.Prepare(app_parameters, provider_client_token, client_id);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (Properties::UsePrivacyMode(session_id_)) {
|
|
if (!service_certificate_.has_certificate()) {
|
|
LOGE("Service certificate not staged");
|
|
return CdmResponseType(PRIVACY_MODE_ERROR_3);
|
|
}
|
|
EncryptedClientIdentification* encrypted_client_id =
|
|
license_request->mutable_encrypted_client_id();
|
|
status = service_certificate_.EncryptClientId(crypto_session_, client_id,
|
|
encrypted_client_id);
|
|
if (status != NO_ERROR) {
|
|
LOGE("Failed to encrypt client ID: status = %s",
|
|
status.ToString().c_str());
|
|
license_request->clear_encrypted_client_id();
|
|
return status;
|
|
}
|
|
license_request->clear_client_id();
|
|
}
|
|
|
|
std::string client_version;
|
|
if (Properties::GetWVCdmVersion(&client_version)) {
|
|
license_request->set_client_version(std::move(client_version));
|
|
}
|
|
|
|
return CdmResponseType(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_WidevinePsshData* widevine_pssh_data =
|
|
content_id->mutable_widevine_pssh_data();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
widevine_pssh_data->add_pssh_data(init_data.data());
|
|
} else {
|
|
LOGE("ISO-CENC init data not available");
|
|
return CdmResponseType(CENC_INIT_DATA_UNAVAILABLE);
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, widevine_pssh_data)) {
|
|
return CdmResponseType(PREPARE_CENC_CONTENT_ID_FAILED);
|
|
}
|
|
} else if (init_data.is_webm()) {
|
|
LicenseRequest_ContentIdentification_WebmKeyId* webm_key_id =
|
|
content_id->mutable_webm_key_id();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
webm_key_id->set_header(init_data.data());
|
|
} else {
|
|
LOGE("WebM init data not available");
|
|
return CdmResponseType(WEBM_INIT_DATA_UNAVAILABLE);
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, webm_key_id)) {
|
|
return CdmResponseType(PREPARE_WEBM_CONTENT_ID_FAILED);
|
|
}
|
|
} else {
|
|
LOGE("Unsupported init data type: type = %s", init_data.type().c_str());
|
|
return CdmResponseType(UNSUPPORTED_INIT_DATA_FORMAT);
|
|
}
|
|
return CdmResponseType(NO_ERROR);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleContentKeyResponse(
|
|
bool is_restore, const std::string& session_key, const std::string& msg,
|
|
const std::string& core_message, const std::string& signature,
|
|
const std::vector<CryptoKey>& key_array,
|
|
const video_widevine::License& license) {
|
|
if (key_array.empty()) {
|
|
LOGE("No content keys provided");
|
|
return CdmResponseType(NO_CONTENT_KEY);
|
|
}
|
|
const CdmResponseType resp = crypto_session_->LoadLicense(
|
|
protocol_version_ <= video_widevine::VERSION_2_1
|
|
? key_request_
|
|
: Sha512Hash(key_request_),
|
|
session_key, msg, core_message, signature, kLicenseKeyTypeContent);
|
|
if (KEY_ADDED == resp) {
|
|
loaded_keys_.clear();
|
|
for (const CryptoKey& key : key_array) {
|
|
loaded_keys_.insert(key.key_id());
|
|
}
|
|
policy_engine_->SetLicense(license, is_restore);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleEntitlementKeyResponse(
|
|
bool is_restore, const std::string& session_key, const std::string& msg,
|
|
const std::string& core_message, const std::string& signature,
|
|
const std::vector<CryptoKey>& key_array,
|
|
const video_widevine::License& license) {
|
|
if (key_array.empty()) {
|
|
LOGE("No entitlement keys provided");
|
|
return CdmResponseType(NO_CONTENT_KEY);
|
|
}
|
|
const CdmResponseType resp = crypto_session_->LoadLicense(
|
|
protocol_version_ <= video_widevine::VERSION_2_1
|
|
? key_request_
|
|
: Sha512Hash(key_request_),
|
|
session_key, msg, core_message, signature, kLicenseKeyTypeEntitlement);
|
|
|
|
if (KEY_ADDED != resp) {
|
|
return resp;
|
|
}
|
|
|
|
// Save the entitlement keys for future use to handle key changes.
|
|
entitlement_keys_.CopyFrom(license.key());
|
|
policy_engine_->SetLicense(license, is_restore);
|
|
return HandleNewEntitledKeys(wrapped_keys_);
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleNewEntitledKeys(
|
|
const std::vector<WidevinePsshData_EntitledKey>& wrapped_keys) {
|
|
std::vector<CryptoKey> entitled_key_array;
|
|
entitled_key_array.reserve(wrapped_keys.size());
|
|
for (const auto& kc : entitlement_keys_) {
|
|
if (kc.type() != video_widevine::License::KeyContainer::ENTITLEMENT) {
|
|
continue;
|
|
}
|
|
for (const auto& wk : wrapped_keys) {
|
|
if (wk.entitlement_key_id() != kc.id()) continue;
|
|
// Strip PKCS#5 padding from entitled content keys.
|
|
std::string content_key = wk.key();
|
|
if (content_key.size() < CONTENT_KEY_SIZE) {
|
|
LOGE(
|
|
"Entitled content key too small: "
|
|
"expected = %zu, actual = %zu (bytes)",
|
|
CONTENT_KEY_SIZE, content_key.size());
|
|
return CdmResponseType(KEY_SIZE_ERROR_2);
|
|
} else if (content_key.size() > CONTENT_KEY_SIZE) {
|
|
content_key.resize(CONTENT_KEY_SIZE);
|
|
}
|
|
|
|
CryptoKey this_entry;
|
|
this_entry.set_key_id(wk.key_id());
|
|
this_entry.set_key_data_iv(wk.iv());
|
|
this_entry.set_entitlement_key_id(wk.entitlement_key_id());
|
|
this_entry.set_key_data(content_key);
|
|
// Add a new entry to the key array to load oemcrypto.
|
|
entitled_key_array.push_back(std::move(this_entry));
|
|
}
|
|
}
|
|
|
|
const CdmResponseType resp =
|
|
crypto_session_->LoadEntitledContentKeys(entitled_key_array);
|
|
if (resp != KEY_ADDED) return resp;
|
|
for (const auto& wk : wrapped_keys) {
|
|
loaded_keys_.insert(wk.key_id());
|
|
}
|
|
policy_engine_->SetEntitledLicenseKeys(wrapped_keys);
|
|
return CdmResponseType(KEY_ADDED);
|
|
}
|
|
|
|
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("Unknown license type: %d", static_cast<int>(license_type));
|
|
return false;
|
|
}
|
|
|
|
content_id->set_request_id(request_id);
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvcdm
|