Files
android/libwvdrmengine/cdm/core/src/license.cpp
Alex Dale b1fad7f4cb Reset crypto session pointers on RemoveKeys.
[ 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
2024-03-14 00:19:25 -07:00

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