(This is a merge of http://go/wvgerrit/84510) When the CE CDM 3.5 behavior around service certificates was originally implemented, it allowed sessions to be created if a service certificate had not yet been installed, in keeping with the EME spec. However, the service certificate in use at session creation time was cached, and so there was a bug where any sessions open before a service certificate was installed would never be updated with any future service certificates. The code also caused problems for Android. When it was merged to master, it was fixed to simply not allow session creation on CE CDM without a service certificate. However, this created an impedance mismatch between the CE CDM and EME that has caused pain for Shaka Player Embedded, Chrome, Chromecast, Fuchsia, and likely every partner that is trying to implement a fully-compliant EME stack on top of CE CDM. Removing the code that blocks session creation without a service certificate is easy. Fixing the bug that motivated it is not. Removing the caching is not possible because Android needs it for certain behavior on its end. So instead, the CE CDM will have to iterate over all open sessions and update their service certificates if the installed service certificate changes. Test: CE CDM Unit Tests Test: Android Unit Tests Bug: 111766009 Change-Id: I1bd70553e2209b823a6acdc221c0497a5f3181b2
1139 lines
37 KiB
C++
1139 lines
37 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine Master
|
|
// 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 "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;
|
|
|
|
} // namespace
|
|
|
|
namespace wvcdm {
|
|
|
|
// Protobuf generated classes.
|
|
using video_widevine::ClientIdentification_ClientCapabilities;
|
|
using video_widevine::ClientIdentification_NameValue;
|
|
using video_widevine::DrmDeviceCertificate;
|
|
using video_widevine::EncryptedClientIdentification;
|
|
using video_widevine::License;
|
|
using video_widevine::License_KeyContainer;
|
|
using video_widevine::LicenseError;
|
|
using video_widevine::LicenseIdentification;
|
|
using video_widevine::LicenseRequest;
|
|
using video_widevine::LicenseRequest_ContentIdentification;
|
|
using video_widevine::LicenseRequest_ContentIdentification_CencDeprecated;
|
|
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
|
|
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
|
|
using video_widevine::SignedDrmDeviceCertificate;
|
|
using video_widevine::SignedMessage;
|
|
|
|
static std::vector<CryptoKey> ExtractEntitlementKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
size_t length = 0;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::ENTITLEMENT: {
|
|
// Strip off PKCS#5 padding - since we know the key is 32 or 48 bytes,
|
|
// the padding will always be 16 bytes.
|
|
if (license.key(i).key().size() > 32) {
|
|
length = license.key(i).key().size() - 16;
|
|
} else {
|
|
length = 0;
|
|
}
|
|
key.set_key_data(license.key(i).key().substr(0, length));
|
|
key.set_key_data_iv(license.key(i).iv());
|
|
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;
|
|
}
|
|
|
|
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
// Extract content key(s)
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
size_t length;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::CONTENT:
|
|
case License_KeyContainer::OPERATOR_SESSION: {
|
|
key.set_key_id(license.key(i).id());
|
|
// Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes,
|
|
// the padding will always be 16 bytes.
|
|
// TODO(b/111069024): remove this!
|
|
if (license.key(i).key().size() > 16) {
|
|
length = license.key(i).key().size() - 16;
|
|
} else {
|
|
length = 0;
|
|
}
|
|
key.set_key_data(license.key(i).key().substr(0, length));
|
|
key.set_key_data_iv(license.key(i).iv());
|
|
if (license.key(i).has_key_control()) {
|
|
key.set_key_control(license.key(i).key_control().key_control_block());
|
|
key.set_key_control_iv(license.key(i).key_control().iv());
|
|
}
|
|
uint32_t four_cc = kFourCcCenc;
|
|
if (license.has_protection_scheme()) {
|
|
four_cc = license.protection_scheme();
|
|
}
|
|
key.set_track_label(license.key(i).track_label());
|
|
switch (four_cc) {
|
|
// b/30713238: Android N assumed that the "protection scheme" Four
|
|
// CC code, after being extracted from the protobuf, was host byte
|
|
// order dependent. Later versions do not assume this, and thus,
|
|
// for backwards compatibility, must support both byte orders.
|
|
case kFourCcCbc1:
|
|
case kFourCcCbcs:
|
|
case kFourCcLittleEndianCbc1:
|
|
case kFourCcLittleEndianCbcs:
|
|
key.set_cipher_mode(kCipherModeCbc);
|
|
break;
|
|
default:
|
|
key.set_cipher_mode(kCipherModeCtr);
|
|
break;
|
|
}
|
|
key_array.push_back(key);
|
|
break;
|
|
}
|
|
case License_KeyContainer::KEY_CONTROL:
|
|
if (license.key(i).has_key_control()) {
|
|
key.set_key_control(license.key(i).key_control().key_control_block());
|
|
if (license.key(i).key_control().has_iv()) {
|
|
key.set_key_control_iv(license.key(i).key_control().iv());
|
|
}
|
|
key_array.push_back(key);
|
|
}
|
|
break;
|
|
default:
|
|
// Ignore SIGNING and SUB_SESSION key types as they are not content
|
|
// related.
|
|
break;
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
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 Clock()),
|
|
license_key_type_(kLicenseKeyTypeContent) {}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id, 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(const std::string& client_token,
|
|
CdmClientTokenType client_token_type,
|
|
const std::string& device_id, 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 (client_token.size() == 0) {
|
|
LOGE("Client token 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;
|
|
}
|
|
|
|
client_token_ = client_token;
|
|
client_token_type_ = client_token_type;
|
|
device_id_ = device_id;
|
|
crypto_session_ = session;
|
|
policy_engine_ = policy_engine;
|
|
use_privacy_mode_ = use_privacy_mode;
|
|
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, CdmLicenseType license_type,
|
|
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_4;
|
|
}
|
|
if (init_data.IsEmpty() && stored_init_data_) {
|
|
InitializationData restored_init_data = *stored_init_data_;
|
|
stored_init_data_.reset();
|
|
return PrepareKeyRequest(restored_init_data, 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 INVALID_PARAMETERS_LIC_3;
|
|
}
|
|
if (init_data.IsEmpty()) {
|
|
LOGE("Init data is empty");
|
|
return INVALID_PARAMETERS_LIC_4;
|
|
}
|
|
if (signed_request == nullptr) {
|
|
LOGE("Output parameter |signed_request| not provided");
|
|
return INVALID_PARAMETERS_LIC_6;
|
|
}
|
|
if (server_url == nullptr) {
|
|
LOGE("Output parameter |server_url| not provided");
|
|
return 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 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 LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR;
|
|
}
|
|
|
|
return 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.
|
|
uint32_t nonce;
|
|
status = crypto_session_->GenerateNonce(&nonce);
|
|
|
|
switch (status) {
|
|
case NO_ERROR:
|
|
break;
|
|
case SESSION_LOST_STATE_ERROR:
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
return status;
|
|
default:
|
|
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("nonce = %u", nonce);
|
|
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
key_request_ = serialized_license_req;
|
|
|
|
// Derive signing and encryption keys and construct signature.
|
|
std::string license_request_signature;
|
|
status = crypto_session_->PrepareRequest(serialized_license_req, false,
|
|
&license_request_signature);
|
|
|
|
if (status != NO_ERROR) {
|
|
signed_request->clear();
|
|
return status;
|
|
}
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("License request signature is empty");
|
|
signed_request->clear();
|
|
return EMPTY_LICENSE_REQUEST;
|
|
}
|
|
|
|
// Put serialized license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
|
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
|
CdmSession* cdm_session, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_1;
|
|
}
|
|
if (signed_request == nullptr) {
|
|
LOGE("Output parameter |signed_request| not provided");
|
|
return INVALID_PARAMETERS_LIC_1;
|
|
}
|
|
if (server_url == nullptr) {
|
|
LOGE("Output parameter |server_url| not provided");
|
|
return INVALID_PARAMETERS_LIC_2;
|
|
}
|
|
|
|
if (is_renewal && !policy_engine_->CanRenew()) {
|
|
LOGE("License renewal prohibited");
|
|
return LICENSE_RENEWAL_PROHIBITED;
|
|
}
|
|
|
|
if (renew_with_client_id_) {
|
|
if (use_privacy_mode_ && !service_certificate_.has_certificate()) {
|
|
LOGE("Privacy mode failure: No service certificate");
|
|
return PRIVACY_MODE_ERROR_2;
|
|
}
|
|
}
|
|
|
|
LicenseRequest license_request;
|
|
if (is_renewal)
|
|
license_request.set_type(LicenseRequest::RENEWAL);
|
|
else
|
|
license_request.set_type(LicenseRequest::RELEASE);
|
|
|
|
license_request.set_request_time(clock_->GetCurrentTime());
|
|
|
|
if (renew_with_client_id_) {
|
|
CdmResponseType status = PrepareClientId(
|
|
app_parameters, 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->get_usage_support_type() == kUsageEntrySupport) {
|
|
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
std::string usage_report;
|
|
CdmResponseType status = crypto_session_->GenerateUsageReport(
|
|
provider_session_token_, &usage_report, &usage_duration_status,
|
|
&seconds_since_started, &seconds_since_last_played);
|
|
if (!is_renewal) {
|
|
if (NO_ERROR == status)
|
|
current_license->set_session_usage_table_entry(usage_report);
|
|
else
|
|
return GENERATE_USAGE_REPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid != usage_duration_status) {
|
|
if (policy_engine_->GetSecondsSinceStarted(&seconds_since_started) &&
|
|
policy_engine_->GetSecondsSinceLastPlayed(&seconds_since_last_played))
|
|
usage_duration_status = CryptoSession::kUsageDurationsValid;
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid == usage_duration_status) {
|
|
current_license->set_seconds_since_started(seconds_since_started);
|
|
current_license->set_seconds_since_last_played(seconds_since_last_played);
|
|
}
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
CdmResponseType status = crypto_session_->GenerateNonce(&nonce);
|
|
|
|
switch (status) {
|
|
case NO_ERROR:
|
|
break;
|
|
case SESSION_LOST_STATE_ERROR:
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
return status;
|
|
default:
|
|
return LICENSE_RENEWAL_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("nonce = %u", nonce);
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
// Construct signature.
|
|
std::string license_request_signature;
|
|
status = crypto_session_->PrepareRenewalRequest(serialized_license_req,
|
|
&license_request_signature);
|
|
if (status != NO_ERROR) return status;
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("License request signature is empty");
|
|
return EMPTY_LICENSE_RENEWAL;
|
|
}
|
|
|
|
// Put serialize license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyResponse(
|
|
const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return 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 EMPTY_LICENSE_RESPONSE_1;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Unable to parse signed license response");
|
|
return 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) ? 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 INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("License response is not signed");
|
|
return LICENSE_RESPONSE_NOT_SIGNED;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("Unable to parse license response");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_1;
|
|
}
|
|
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("Signed response has no session keys present");
|
|
return 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(), 2 * MAC_KEY_SIZE);
|
|
}
|
|
}
|
|
if (license.policy().can_renew() ||
|
|
(mac_key_iv.size() != 0 || mac_keys.size() != 0)) {
|
|
if (mac_key_iv.size() != KEY_IV_SIZE ||
|
|
mac_keys.size() != 2 * MAC_KEY_SIZE) {
|
|
LOGE(
|
|
"MAC key/IV size error: expected = %lu/%lu, "
|
|
"actual = %zu/%zu (key/iv)",
|
|
2 * MAC_KEY_SIZE, KEY_IV_SIZE, mac_keys.size(), mac_key_iv.size());
|
|
return KEY_SIZE_ERROR_1;
|
|
}
|
|
}
|
|
|
|
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 NO_CONTENT_KEY;
|
|
}
|
|
license_key_type_ = key_type;
|
|
|
|
if (license.has_provider_client_token())
|
|
provider_client_token_ = license.provider_client_token();
|
|
|
|
if (license.has_srm_update()) {
|
|
status = crypto_session_->LoadSrm(license.srm_update());
|
|
switch (status) {
|
|
case NO_ERROR:
|
|
break;
|
|
case SYSTEM_INVALIDATED_ERROR:
|
|
return status;
|
|
default:
|
|
break; // Ignore
|
|
}
|
|
}
|
|
|
|
if (license.id().type() == video_widevine::OFFLINE &&
|
|
license.policy().can_persist())
|
|
is_offline_ = true;
|
|
|
|
if (license.id().has_provider_session_token())
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
|
|
LOGV("provider_session_token = %s", provider_session_token_.empty()
|
|
? "N/A"
|
|
: provider_session_token_.c_str());
|
|
|
|
if (license.policy().has_renewal_server_url()) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
CdmResponseType resp = NO_CONTENT_KEY;
|
|
if (kLicenseKeyTypeEntitlement == key_type) {
|
|
resp = HandleEntitlementKeyResponse(signed_response.msg(),
|
|
signed_response.signature(), mac_key_iv,
|
|
mac_keys, key_array, license);
|
|
} else if (kLicenseKeyTypeContent == key_type) {
|
|
resp = HandleContentKeyResponse(signed_response.msg(),
|
|
signed_response.signature(), mac_key_iv,
|
|
mac_keys, key_array, license);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
|
bool is_renewal, const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_3;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return EMPTY_LICENSE_RESPONSE_2;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("Unable to parse signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_2;
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE("Unrecognized signed message type: type = %d",
|
|
static_cast<int>(signed_response.type()));
|
|
return INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("Update key response is missing signature");
|
|
return SIGNATURE_NOT_FOUND;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("Unable to parse license from signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_3;
|
|
}
|
|
|
|
if (!license.has_id()) {
|
|
LOGE("License ID not present");
|
|
return LICENSE_ID_NOT_FOUND;
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
if (!is_renewal) {
|
|
if (!license.id().has_provider_session_token()) return KEY_ADDED;
|
|
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
CdmResponseType status = crypto_session_->ReleaseUsageInformation(
|
|
signed_response.msg(), signed_response.signature(),
|
|
provider_session_token_);
|
|
return (NO_ERROR == status) ? KEY_ADDED : status;
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url() &&
|
|
license.policy().renewal_server_url().size() > 0) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
|
|
CdmResponseType status = crypto_session_->RefreshKeys(
|
|
signed_response.msg(), signed_response.signature(), key_array.size(),
|
|
&key_array[0]);
|
|
|
|
if (status == KEY_ADDED) {
|
|
policy_engine_->UpdateLicense(license);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleEmbeddedKeyData(
|
|
const InitializationData& init_data) {
|
|
return HandleNewEntitledKeys(init_data.ExtractWrappedKeys());
|
|
}
|
|
|
|
CdmResponseType CdmLicense::RestoreOfflineLicense(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response,
|
|
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
|
|
int64_t last_playback_time, int64_t grace_period_end_time,
|
|
CdmSession* cdm_session) {
|
|
if (license_request.empty()) {
|
|
LOGE("License request is empty");
|
|
return EMPTY_LICENSE_REQUEST_2;
|
|
}
|
|
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return EMPTY_LICENSE_RESPONSE_3;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("Failed to parse license request");
|
|
return 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 INVALID_LICENSE_REQUEST_TYPE_1;
|
|
}
|
|
|
|
key_request_ = signed_request.msg();
|
|
CdmResponseType sts = HandleKeyResponse(license_response);
|
|
|
|
if (sts != KEY_ADDED) return sts;
|
|
|
|
if (!license_renewal_response.empty()) {
|
|
sts = HandleKeyUpdateResponse(true, license_renewal_response);
|
|
if (sts != KEY_ADDED) return sts;
|
|
}
|
|
|
|
if (!provider_session_token_.empty()) {
|
|
if (cdm_session &&
|
|
cdm_session->get_usage_support_type() == kUsageEntrySupport) {
|
|
CdmResponseType status = cdm_session->UpdateUsageEntryInformation();
|
|
if (NO_ERROR != status) return 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();
|
|
if (current_time - seconds_since_started > 0)
|
|
playback_start_time = current_time - seconds_since_started;
|
|
if (current_time - last_playback_time > 0)
|
|
last_playback_time = current_time - seconds_since_last_played;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
policy_engine_->RestorePlaybackTimes(playback_start_time, last_playback_time,
|
|
grace_period_end_time);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::RestoreLicenseForRelease(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response) {
|
|
if (license_request.empty()) {
|
|
LOGE("License request is empty");
|
|
return EMPTY_LICENSE_REQUEST_3;
|
|
}
|
|
|
|
if (license_response.empty()) {
|
|
LOGE("License response is empty");
|
|
return EMPTY_LICENSE_RESPONSE_4;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("Failed to parse signed license request");
|
|
return 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 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 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 INVALID_LICENSE_TYPE_2;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("License response is not signed");
|
|
return SIGNATURE_NOT_FOUND_2;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("Failed to parse license response");
|
|
return 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 SESSION_KEYS_NOT_FOUND_2;
|
|
}
|
|
|
|
if (!license.id().has_provider_session_token()) {
|
|
CdmResponseType result = HandleKeyResponse(license_response);
|
|
return result == KEY_ADDED ? 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 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 KEY_ERROR;
|
|
}
|
|
|
|
switch (license_error.error_code()) {
|
|
case LicenseError::INVALID_DRM_DEVICE_CERTIFICATE:
|
|
return NEED_PROVISIONING;
|
|
case LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE:
|
|
return DEVICE_REVOKED;
|
|
case LicenseError::SERVICE_UNAVAILABLE:
|
|
default:
|
|
LOGW("Unknown error type: error_code = %d",
|
|
static_cast<int>(license_error.error_code()));
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareClientId(
|
|
const CdmAppParameterMap& app_parameters,
|
|
const std::string& provider_client_token, LicenseRequest* license_request) {
|
|
wvcdm::ClientIdentification id;
|
|
CdmResponseType status = id.Init(client_token_, device_id_, 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 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 NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareContentId(
|
|
const InitializationData& init_data, CdmLicenseType license_type,
|
|
const std::string& request_id, LicenseRequest* license_request) {
|
|
// Content Identification may be a cenc_id, a webm_id or a license_id
|
|
LicenseRequest_ContentIdentification* content_id =
|
|
license_request->mutable_content_id();
|
|
|
|
if (init_data.is_cenc() || init_data.is_hls()) {
|
|
LicenseRequest_ContentIdentification_CencDeprecated* cenc_content_id =
|
|
content_id->mutable_cenc_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
cenc_content_id->add_pssh(init_data.data());
|
|
} else {
|
|
LOGE("ISO-CENC init data not available");
|
|
return CENC_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, cenc_content_id)) {
|
|
return PREPARE_CENC_CONTENT_ID_FAILED;
|
|
}
|
|
} else if (init_data.is_webm()) {
|
|
LicenseRequest_ContentIdentification_WebmDeprecated* webm_content_id =
|
|
content_id->mutable_webm_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
webm_content_id->set_header(init_data.data());
|
|
} else {
|
|
LOGE("WebM init data not available");
|
|
return WEBM_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, webm_content_id)) {
|
|
return PREPARE_WEBM_CONTENT_ID_FAILED;
|
|
}
|
|
} else {
|
|
LOGE("Unsupported init data type: type = %s", init_data.type().c_str());
|
|
return UNSUPPORTED_INIT_DATA_FORMAT;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleContentKeyResponse(
|
|
const std::string& msg, const std::string& signature,
|
|
const std::string& mac_key_iv, const std::string& mac_key,
|
|
const std::vector<CryptoKey>& key_array,
|
|
const video_widevine::License& license) {
|
|
if (key_array.empty()) {
|
|
LOGE("No content keys provided");
|
|
return NO_CONTENT_KEY;
|
|
}
|
|
CdmResponseType resp = crypto_session_->LoadKeys(
|
|
msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_,
|
|
license.srm_requirement(), kLicenseKeyTypeContent);
|
|
|
|
if (KEY_ADDED == resp) {
|
|
loaded_keys_.clear();
|
|
for (std::vector<CryptoKey>::const_iterator it = key_array.begin();
|
|
it != key_array.end(); ++it) {
|
|
loaded_keys_.insert(it->key_id());
|
|
}
|
|
policy_engine_->SetLicense(license);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleEntitlementKeyResponse(
|
|
const std::string& msg, const std::string& signature,
|
|
const std::string& mac_key_iv, const std::string& mac_key,
|
|
const std::vector<CryptoKey>& key_array,
|
|
const video_widevine::License& license) {
|
|
if (key_array.empty()) {
|
|
LOGE("No entitlement keys provided");
|
|
return NO_CONTENT_KEY;
|
|
}
|
|
CdmResponseType resp = crypto_session_->LoadKeys(
|
|
msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_,
|
|
license.srm_requirement(), 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);
|
|
|
|
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 (RepeatedPtrField<License_KeyContainer>::const_iterator kc =
|
|
entitlement_keys_.begin();
|
|
kc != entitlement_keys_.end(); ++kc) {
|
|
if (kc->type() != video_widevine::License::KeyContainer::ENTITLEMENT) {
|
|
continue;
|
|
}
|
|
for (std::vector<WidevinePsshData_EntitledKey>::const_iterator wk =
|
|
wrapped_keys.begin();
|
|
wk != wrapped_keys.end(); wk++) {
|
|
if (wk->entitlement_key_id() == kc->id()) {
|
|
// Add a new entry to the key array to load oemcrypto.
|
|
entitled_key_array.resize(entitled_key_array.size() + 1);
|
|
|
|
// 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 = %lu, actual = %zu (bytes)",
|
|
CONTENT_KEY_SIZE, content_key.size());
|
|
return KEY_SIZE_ERROR_2;
|
|
} else if (content_key.size() > CONTENT_KEY_SIZE) {
|
|
content_key.resize(CONTENT_KEY_SIZE);
|
|
}
|
|
|
|
CryptoKey& this_entry = entitled_key_array.back();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
CdmResponseType resp =
|
|
crypto_session_->LoadEntitledContentKeys(entitled_key_array);
|
|
if (KEY_ADDED == resp) {
|
|
for (std::vector<WidevinePsshData_EntitledKey>::const_iterator it =
|
|
wrapped_keys.begin();
|
|
it != wrapped_keys.end(); ++it) {
|
|
loaded_keys_.insert(it->key_id());
|
|
}
|
|
|
|
policy_engine_->SetEntitledLicenseKeys(wrapped_keys);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
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
|