Files
android/libwvdrmengine/cdm/core/src/license.cpp
John "Juce" Bruce 340810f77f Remove hash algorithm from renewal requests
(Merged from http://go/wvgerrit/165859.)

Since renewal requests are signed with the MAC keys and not an
asymmetric key, it does not make sense to query OEMCrypto for the
asymmetric key hash algorithm nor to include the result in the renewal
request.

Bug: 262427121
Test: opk_ta
Change-Id: Ib309b63b79e553f4754c013718df242247ab9488
2023-02-24 21:11:50 +00:00

1185 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) {
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.
if (license.key(i).key().size() !=
CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING &&
license.key(i).key().size() !=
MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_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 + LICENSE_PROTOCOL_2_1_PADDING,
MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING,
license.key(i).key().size());
continue;
}
const size_t length =
license.key(i).key().size() - LICENSE_PROTOCOL_2_1_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)
: crypto_session_(nullptr),
policy_engine_(nullptr),
session_id_(session_id),
initialized_(false),
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)
: crypto_session_(nullptr),
policy_engine_(nullptr),
session_id_(session_id),
initialized_(false),
renew_with_client_id_(false),
is_offline_(false),
use_privacy_mode_(false),
license_key_type_(kLicenseKeyTypeContent) {
clock_.reset(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;
}
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.Enum()) {
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(video_widevine::VERSION_2_1);
// 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(video_widevine::VERSION_2_1);
// 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);
}
CdmResponseType status = crypto_session_->GenerateDerivedKeys(
key_request_, signed_response.session_key());
if (status != NO_ERROR) return status;
// 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);
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.
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_message, core_message,
signature, key_array, license);
} else if (kLicenseKeyTypeContent == key_type) {
resp = HandleContentKeyResponse(is_restore, 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();
}
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 (NO_ERROR == status) {
license_request->clear_client_id();
} else {
license_request->clear_encrypted_client_id();
}
return status;
}
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& 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(
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& 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(
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