(This is a merge of wvgerrit/25582) Provisioning 3.0 devices that do not use SPOIDs have been returning their full OEM Public Certificate as their device ID. While this is not a security concern, (it is a PUBLIC cert) the cert is many times larger than applications are likely expecting. (several kilobytes vs. just a few bytes) This patch hashes the OEM Public Certificate to produce a smaller value, but only when it is being provided out of the CDM to a caller. Bug: 34716264 Test: run_all_unit_tests.sh Change-Id: Ib82cf7a174a8bf02ff606edd0394ada13842224c
1100 lines
37 KiB
C++
1100 lines
37 KiB
C++
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
|
|
#include "license.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "clock.h"
|
|
#include "crypto_key.h"
|
|
#include "crypto_session.h"
|
|
#include "device_files.h"
|
|
#include "log.h"
|
|
#include "policy_engine.h"
|
|
#include "privacy_crypto.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace {
|
|
std::string kCompanyNameKey = "company_name";
|
|
std::string kModelNameKey = "model_name";
|
|
std::string kArchitectureNameKey = "architecture_name";
|
|
std::string kDeviceNameKey = "device_name";
|
|
std::string kProductNameKey = "product_name";
|
|
std::string kBuildInfoKey = "build_info";
|
|
std::string kDeviceIdKey = "device_id";
|
|
std::string kWVCdmVersionKey = "widevine_cdm_version";
|
|
std::string kOemCryptoSecurityPatchLevelKey = "oem_crypto_security_patch_level";
|
|
} // 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 uint32_t kFourCcCens = 0x63656e73;
|
|
|
|
namespace wvcdm {
|
|
|
|
// Protobuf generated classes.
|
|
using video_widevine::ClientIdentification;
|
|
using video_widevine::ClientIdentification_ClientCapabilities;
|
|
using video_widevine::
|
|
ClientIdentification_ClientCapabilities_CertificateKeyType;
|
|
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_WebmDeprecated;
|
|
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
|
|
using video_widevine::SignedDrmDeviceCertificate;
|
|
using video_widevine::SignedMessage;
|
|
|
|
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
|
std::vector<CryptoKey> key_array;
|
|
|
|
// Extract content key(s)
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
CryptoKey key;
|
|
size_t length;
|
|
switch (license.key(i).type()) {
|
|
case License_KeyContainer::CONTENT:
|
|
case License_KeyContainer::OPERATOR_SESSION: {
|
|
key.set_key_id(license.key(i).id());
|
|
// Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes,
|
|
// the padding will always be 16 bytes.
|
|
if (license.key(i).key().size() > 16) {
|
|
length = license.key(i).key().size() - 16;
|
|
} else {
|
|
length = 0;
|
|
}
|
|
key.set_key_data(license.key(i).key().substr(0, length));
|
|
key.set_key_data_iv(license.key(i).iv());
|
|
if (license.key(i).has_key_control()) {
|
|
key.set_key_control(license.key(i).key_control().key_control_block());
|
|
key.set_key_control_iv(license.key(i).key_control().iv());
|
|
}
|
|
uint32_t four_cc = kFourCcCenc;
|
|
if (license.has_protection_scheme()) {
|
|
four_cc = license.protection_scheme();
|
|
}
|
|
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 key types as they are not content related
|
|
break;
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
|
: crypto_session_(NULL),
|
|
policy_engine_(NULL),
|
|
session_id_(session_id),
|
|
initialized_(false),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false),
|
|
clock_(new Clock()) {}
|
|
|
|
CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock)
|
|
: crypto_session_(NULL),
|
|
policy_engine_(NULL),
|
|
session_id_(session_id),
|
|
initialized_(false),
|
|
renew_with_client_id_(false),
|
|
is_offline_(false) {
|
|
clock_.reset(clock);
|
|
}
|
|
|
|
CdmLicense::~CdmLicense() {}
|
|
|
|
bool CdmLicense::Init(
|
|
const std::string& client_token, CdmClientTokenType client_token_type,
|
|
CryptoSession* session, PolicyEngine* policy_engine) {
|
|
if (clock_.get() == NULL) {
|
|
LOGE("CdmLicense::Init: clock parameter not provided");
|
|
return false;
|
|
}
|
|
if (session_id_.empty()) {
|
|
LOGE("CdmLicense::Init: empty session id provided");
|
|
return false;
|
|
}
|
|
if (client_token.size() == 0) {
|
|
LOGE("CdmLicense::Init: empty client token provided");
|
|
return false;
|
|
}
|
|
if (session == NULL || !session->IsOpen()) {
|
|
LOGE("CdmLicense::Init: crypto session not provided or not open");
|
|
return false;
|
|
}
|
|
if (policy_engine == NULL) {
|
|
LOGE("CdmLicense::Init: no policy engine provided");
|
|
return false;
|
|
}
|
|
|
|
client_token_ = client_token;
|
|
client_token_type_ = client_token_type;
|
|
crypto_session_ = session;
|
|
policy_engine_ = policy_engine;
|
|
service_certificate_.reset(new ServiceCertificate());
|
|
if (service_certificate_.get() == NULL) {
|
|
LOGE("CdmLicense::Init: creation of service_certificate failed");
|
|
return false;
|
|
}
|
|
if (!service_certificate_->Init(session_id_, crypto_session_)) {
|
|
LOGE("CdmLicense::Init: init of service_certificate failed");
|
|
return false;
|
|
}
|
|
initialized_ = true;
|
|
return true;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyRequest(
|
|
const InitializationData& init_data, CdmLicenseType license_type,
|
|
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_4;
|
|
}
|
|
if (init_data.IsEmpty() && stored_init_data_.get()) {
|
|
InitializationData restored_init_data = *stored_init_data_;
|
|
stored_init_data_.reset();
|
|
return PrepareKeyRequest(restored_init_data, license_type, app_parameters,
|
|
signed_request, server_url);
|
|
}
|
|
if (!init_data.is_supported()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: unsupported init data type (%s)",
|
|
init_data.type().c_str());
|
|
return INVALID_PARAMETERS_LIC_3;
|
|
}
|
|
if (init_data.IsEmpty()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: empty init data provided");
|
|
return INVALID_PARAMETERS_LIC_4;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: no signed request provided");
|
|
return INVALID_PARAMETERS_LIC_6;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: no server url provided");
|
|
return INVALID_PARAMETERS_LIC_7;
|
|
}
|
|
|
|
if (service_certificate_->IsRequired() &&
|
|
!service_certificate_->IsAvailable()) {
|
|
stored_init_data_.reset(new InitializationData(init_data));
|
|
*server_url = server_url_;
|
|
if (service_certificate_->PrepareServiceCertificateRequest(signed_request))
|
|
return KEY_MESSAGE;
|
|
else
|
|
return LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR;
|
|
}
|
|
|
|
std::string request_id;
|
|
crypto_session_->GenerateRequestId(&request_id);
|
|
|
|
LicenseRequest license_request;
|
|
CdmResponseType status;
|
|
status = PrepareClientId(app_parameters, &license_request);
|
|
if (NO_ERROR != status) return status;
|
|
|
|
status = PrepareContentId(init_data, license_type, request_id,
|
|
&license_request);
|
|
if (NO_ERROR != status) return status;
|
|
|
|
license_request.set_type(LicenseRequest::NEW);
|
|
|
|
license_request.set_request_time(clock_->GetCurrentTime());
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!crypto_session_->GenerateNonce(&nonce)) {
|
|
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
if (Properties::use_certificates_as_identification())
|
|
key_request_ = serialized_license_req;
|
|
|
|
// Derive signing and encryption keys and construct signature.
|
|
std::string license_request_signature;
|
|
if (!crypto_session_->PrepareRequest(serialized_license_req, false,
|
|
&license_request_signature)) {
|
|
signed_request->clear();
|
|
return LICENSE_REQUEST_SIGNING_ERROR;
|
|
}
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest: License request signature empty");
|
|
signed_request->clear();
|
|
return EMPTY_LICENSE_REQUEST;
|
|
}
|
|
|
|
// Put serialized license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
|
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
|
CdmKeyMessage* signed_request, std::string* server_url) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_1;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided");
|
|
return INVALID_PARAMETERS_LIC_1;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided");
|
|
return INVALID_PARAMETERS_LIC_2;
|
|
}
|
|
|
|
if (is_renewal && !policy_engine_->CanRenew()) {
|
|
LOGE("CdmLicense::PrepareKeyUpdateRequest: license renewal prohibited");
|
|
return LICENSE_RENEWAL_PROHIBITED;
|
|
}
|
|
|
|
if (renew_with_client_id_) {
|
|
if (service_certificate_->IsRequired() &&
|
|
!service_certificate_->IsAvailable()) {
|
|
*server_url = server_url_;
|
|
if (service_certificate_->
|
|
PrepareServiceCertificateRequest(signed_request))
|
|
return KEY_MESSAGE;
|
|
else
|
|
return LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR;
|
|
}
|
|
}
|
|
|
|
LicenseRequest license_request;
|
|
if (is_renewal)
|
|
license_request.set_type(LicenseRequest::RENEWAL);
|
|
else
|
|
license_request.set_type(LicenseRequest::RELEASE);
|
|
|
|
license_request.set_request_time(clock_->GetCurrentTime());
|
|
|
|
if (renew_with_client_id_) {
|
|
CdmResponseType status = PrepareClientId(app_parameters, &license_request);
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
|
license_request.mutable_content_id()->mutable_existing_license();
|
|
LicenseIdentification license_id = policy_engine_->license_id();
|
|
current_license->mutable_license_id()->CopyFrom(license_id);
|
|
|
|
int64_t seconds_since_started, seconds_since_last_played;
|
|
CryptoSession::UsageDurationStatus usage_duration_status =
|
|
CryptoSession::kUsageDurationsInvalid;
|
|
if (!provider_session_token_.empty()) {
|
|
if (!is_renewal) {
|
|
CdmResponseType status =
|
|
crypto_session_->DeactivateUsageInformation(provider_session_token_);
|
|
if (NO_ERROR != status) return status;
|
|
}
|
|
|
|
std::string usage_report;
|
|
CdmResponseType status = crypto_session_->GenerateUsageReport(
|
|
provider_session_token_, &usage_report, &usage_duration_status,
|
|
&seconds_since_started, &seconds_since_last_played);
|
|
if (!is_renewal) {
|
|
if (NO_ERROR == status)
|
|
current_license->set_session_usage_table_entry(usage_report);
|
|
else
|
|
return GENERATE_USAGE_REPORT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid != usage_duration_status) {
|
|
if (policy_engine_->GetSecondsSinceStarted(&seconds_since_started) &&
|
|
policy_engine_->GetSecondsSinceLastPlayed(&seconds_since_last_played))
|
|
usage_duration_status = CryptoSession::kUsageDurationsValid;
|
|
}
|
|
|
|
if (CryptoSession::kUsageDurationsValid == usage_duration_status) {
|
|
current_license->set_seconds_since_started(seconds_since_started);
|
|
current_license->set_seconds_since_last_played(seconds_since_last_played);
|
|
}
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!crypto_session_->GenerateNonce(&nonce)) {
|
|
return LICENSE_RENEWAL_NONCE_GENERATION_ERROR;
|
|
}
|
|
license_request.set_key_control_nonce(nonce);
|
|
LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce);
|
|
license_request.set_protocol_version(video_widevine::VERSION_2_1);
|
|
|
|
// License request is complete. Serialize it.
|
|
std::string serialized_license_req;
|
|
license_request.SerializeToString(&serialized_license_req);
|
|
|
|
// Construct signature.
|
|
std::string license_request_signature;
|
|
if (!crypto_session_->PrepareRenewalRequest(serialized_license_req,
|
|
&license_request_signature))
|
|
return LICENSE_RENEWAL_SIGNING_ERROR;
|
|
|
|
if (license_request_signature.empty()) {
|
|
LOGE(
|
|
"CdmLicense::PrepareKeyUpdateRequest: empty license request"
|
|
" signature");
|
|
return EMPTY_LICENSE_RENEWAL;
|
|
}
|
|
|
|
// Put serialize license request and signature together
|
|
SignedMessage signed_message;
|
|
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
|
signed_message.set_signature(license_request_signature);
|
|
signed_message.set_msg(serialized_license_req);
|
|
|
|
signed_message.SerializeToString(signed_request);
|
|
*server_url = server_url_;
|
|
return KEY_MESSAGE;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyResponse(
|
|
const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::HandleKeyResponse: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_2;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: empty license response");
|
|
return EMPTY_LICENSE_RESPONSE_1;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: unable to parse signed license"
|
|
" response");
|
|
return INVALID_LICENSE_RESPONSE;
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::SERVICE_CERTIFICATE: {
|
|
CdmResponseType status =
|
|
service_certificate_->VerifyAndSet(signed_response.msg());
|
|
if (status != NO_ERROR) {
|
|
return status;
|
|
}
|
|
return NEED_KEY;
|
|
}
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: unrecognized signed message type: %d",
|
|
signed_response.type());
|
|
return INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: license response is not signed");
|
|
return LICENSE_RESPONSE_NOT_SIGNED;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("CdmLicense::HandleKeyResponse: unable to parse license response");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_1;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("CdmLicense::HandleKeyResponse: no session keys present");
|
|
return SESSION_KEYS_NOT_FOUND;
|
|
}
|
|
|
|
if (!crypto_session_->GenerateDerivedKeys(key_request_,
|
|
signed_response.session_key()))
|
|
return GENERATE_DERIVED_KEYS_ERROR;
|
|
}
|
|
|
|
// Extract mac key
|
|
std::string mac_key_iv;
|
|
std::string mac_key;
|
|
for (int i = 0; i < license.key_size(); ++i) {
|
|
if (license.key(i).type() == License_KeyContainer::SIGNING) {
|
|
mac_key_iv.assign(license.key(i).iv());
|
|
|
|
// Strip off PKCS#5 padding
|
|
mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE);
|
|
}
|
|
}
|
|
if (license.policy().can_renew() ||
|
|
(mac_key_iv.size() != 0 || mac_key.size() != 0)) {
|
|
if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyResponse: mac key/iv size error"
|
|
"(key/iv size expected: %d/%d, actual: %d/%d",
|
|
MAC_KEY_SIZE, KEY_IV_SIZE, mac_key.size(), mac_key_iv.size());
|
|
return KEY_SIZE_ERROR;
|
|
}
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
if (!key_array.size()) {
|
|
LOGE("CdmLicense::HandleKeyResponse : No content keys.");
|
|
return NO_CONTENT_KEY;
|
|
}
|
|
|
|
if (license.has_srm_update()) crypto_session_->LoadSrm(license.srm_update());
|
|
|
|
if (license.id().type() == video_widevine::OFFLINE &&
|
|
license.policy().can_persist())
|
|
is_offline_ = true;
|
|
|
|
if (license.id().has_provider_session_token())
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
|
|
if (license.policy().has_renewal_server_url()) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
CdmResponseType resp = crypto_session_->LoadKeys(
|
|
signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key,
|
|
key_array, provider_session_token_, license.srm_requirement());
|
|
|
|
if (KEY_ADDED == resp) {
|
|
loaded_keys_.clear();
|
|
for (std::vector<CryptoKey>::iterator it = key_array.begin();
|
|
it != key_array.end(); ++it) {
|
|
loaded_keys_.insert(it->key_id());
|
|
}
|
|
policy_engine_->SetLicense(license);
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
|
bool is_renewal, const CdmKeyResponse& license_response) {
|
|
if (!initialized_) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: not initialized");
|
|
return LICENSE_PARSER_NOT_INITIALIZED_3;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse : Empty license response.");
|
|
return EMPTY_LICENSE_RESPONSE_2;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_2;
|
|
}
|
|
|
|
switch (signed_response.type()) {
|
|
case SignedMessage::LICENSE:
|
|
break;
|
|
case SignedMessage::SERVICE_CERTIFICATE: {
|
|
CdmResponseType status =
|
|
service_certificate_->VerifyAndSet(signed_response.msg());
|
|
if (status != NO_ERROR) {
|
|
return status;
|
|
}
|
|
return NEED_KEY;
|
|
}
|
|
case SignedMessage::ERROR_RESPONSE:
|
|
return HandleKeyErrorResponse(signed_response);
|
|
default:
|
|
LOGE(
|
|
"CdmLicense::HandleKeyUpdateResponse: unrecognized signed message "
|
|
"type: %d",
|
|
signed_response.type());
|
|
return INVALID_LICENSE_TYPE;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: signature missing");
|
|
return SIGNATURE_NOT_FOUND;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE(
|
|
"CdmLicense::HandleKeyUpdateResponse: Unable to parse license"
|
|
" from signed message");
|
|
return LICENSE_RESPONSE_PARSE_ERROR_3;
|
|
}
|
|
|
|
if (!license.has_id()) {
|
|
LOGE("CdmLicense::HandleKeyUpdateResponse: license id not present");
|
|
return LICENSE_ID_NOT_FOUND;
|
|
}
|
|
|
|
if (license.policy().has_always_include_client_id()) {
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
}
|
|
|
|
if (!is_renewal) {
|
|
if (!license.id().has_provider_session_token()) return KEY_ADDED;
|
|
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
CdmResponseType status = crypto_session_->ReleaseUsageInformation(
|
|
signed_response.msg(), signed_response.signature(),
|
|
provider_session_token_);
|
|
return (NO_ERROR == status) ? KEY_ADDED : status;
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url() &&
|
|
license.policy().renewal_server_url().size() > 0) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
|
|
if (crypto_session_->RefreshKeys(signed_response.msg(),
|
|
signed_response.signature(),
|
|
key_array.size(), &key_array[0])) {
|
|
policy_engine_->UpdateLicense(license);
|
|
|
|
return KEY_ADDED;
|
|
} else {
|
|
return REFRESH_KEYS_ERROR;
|
|
}
|
|
}
|
|
|
|
bool CdmLicense::RestoreOfflineLicense(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response,
|
|
const CdmKeyResponse& license_renewal_response, int64_t playback_start_time,
|
|
int64_t last_playback_time, int64_t grace_period_end_time) {
|
|
if (license_request.empty() || license_response.empty()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreOfflineLicense: key_request or response empty: "
|
|
"%u %u",
|
|
license_request.size(), license_response.size());
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("CdmLicense::RestoreOfflineLicense: license_request parse failed");
|
|
return false;
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE(
|
|
"CdmLicense::RestoreOfflineLicense: license request type: expected = "
|
|
"%d, actual = %d",
|
|
SignedMessage::LICENSE_REQUEST, signed_request.type());
|
|
return false;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
key_request_ = signed_request.msg();
|
|
} else {
|
|
if (!crypto_session_->GenerateDerivedKeys(signed_request.msg())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CdmResponseType sts = HandleKeyResponse(license_response);
|
|
|
|
if (sts != KEY_ADDED) return false;
|
|
|
|
if (!license_renewal_response.empty()) {
|
|
sts = HandleKeyUpdateResponse(true, license_renewal_response);
|
|
|
|
if (sts != KEY_ADDED) return false;
|
|
}
|
|
|
|
if (!provider_session_token_.empty()) {
|
|
std::string usage_report;
|
|
CryptoSession::UsageDurationStatus usage_duration_status =
|
|
CryptoSession::kUsageDurationsInvalid;
|
|
int64_t seconds_since_started, seconds_since_last_played;
|
|
sts = crypto_session_->GenerateUsageReport(
|
|
provider_session_token_, &usage_report, &usage_duration_status,
|
|
&seconds_since_started, &seconds_since_last_played);
|
|
|
|
if (NO_ERROR == sts) {
|
|
switch (usage_duration_status) {
|
|
case CryptoSession::kUsageDurationPlaybackNotBegun:
|
|
playback_start_time = 0;
|
|
last_playback_time = 0;
|
|
break;
|
|
case CryptoSession::kUsageDurationsValid: {
|
|
int64_t current_time = clock_->GetCurrentTime();
|
|
if (current_time - seconds_since_started > 0)
|
|
playback_start_time = current_time - seconds_since_started;
|
|
if (current_time - last_playback_time > 0)
|
|
last_playback_time = current_time - seconds_since_last_played;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
policy_engine_->RestorePlaybackTimes(playback_start_time, last_playback_time,
|
|
grace_period_end_time);
|
|
return true;
|
|
}
|
|
|
|
bool CdmLicense::RestoreLicenseForRelease(
|
|
const CdmKeyMessage& license_request,
|
|
const CdmKeyResponse& license_response) {
|
|
if (license_request.empty() || license_response.empty()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: key_request or response empty:"
|
|
" %u %u",
|
|
license_request.size(), license_response.size());
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_request;
|
|
if (!signed_request.ParseFromString(license_request)) {
|
|
LOGE("CdmLicense::RestoreLicenseForRelease: license_request parse failed");
|
|
return false;
|
|
}
|
|
|
|
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: license request type: expected "
|
|
"= %d, actual = %d",
|
|
SignedMessage::LICENSE_REQUEST, signed_request.type());
|
|
return false;
|
|
}
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
key_request_ = signed_request.msg();
|
|
} else {
|
|
if (!crypto_session_->GenerateDerivedKeys(signed_request.msg())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unable to parse signed license"
|
|
" response");
|
|
return false;
|
|
}
|
|
|
|
if (SignedMessage::LICENSE != signed_response.type()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unrecognized signed message "
|
|
"type: %d",
|
|
signed_response.type());
|
|
return false;
|
|
}
|
|
|
|
if (!signed_response.has_signature()) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: license response is not"
|
|
" signed");
|
|
return false;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE(
|
|
"CdmLicense::RestoreLicenseForRelease: unable to parse license"
|
|
" response");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token())
|
|
provider_session_token_ = license.id().provider_session_token();
|
|
|
|
if (license.policy().has_always_include_client_id())
|
|
renew_with_client_id_ = license.policy().always_include_client_id();
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
if (!signed_response.has_session_key()) {
|
|
LOGE("CdmLicense::RestoreLicenseForRelease: no session keys present");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token()) {
|
|
if (!crypto_session_->GenerateDerivedKeys(key_request_,
|
|
signed_response.session_key()))
|
|
return false;
|
|
} else {
|
|
return KEY_ADDED == HandleKeyResponse(license_response);
|
|
}
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url())
|
|
server_url_ = license.policy().renewal_server_url();
|
|
|
|
// If the policy engine already has keys, they will now expire.
|
|
// If the policy engine does not already have keys, this will not add any.
|
|
policy_engine_->SetLicenseForRelease(license);
|
|
return true;
|
|
}
|
|
|
|
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
|
|
return loaded_keys_.find(key_id) != loaded_keys_.end();
|
|
}
|
|
|
|
bool CdmLicense::ExtractProviderSessionToken(
|
|
const CdmKeyResponse& license_response,
|
|
std::string* provider_session_token) {
|
|
if (license_response.empty()) {
|
|
LOGW("CdmLicense::ExtractProviderSessionToken: empty license response");
|
|
return false;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response)) {
|
|
LOGW(
|
|
"CdmLicense::ExtractProviderSessionToken: unable to parse signed "
|
|
"license response");
|
|
return false;
|
|
}
|
|
|
|
if (signed_response.type() != SignedMessage::LICENSE) {
|
|
LOGW("CdmLicense::ExtractProviderSessionToken: unrecognized signed message "
|
|
"type: %d", signed_response.type());
|
|
return false;
|
|
}
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg())) {
|
|
LOGE("CdmLicense::ExtractProviderSessionToken: unable to parse license "
|
|
"response");
|
|
return false;
|
|
}
|
|
|
|
if (license.id().has_provider_session_token()) {
|
|
*provider_session_token = license.id().provider_session_token();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
|
const SignedMessage& signed_message) {
|
|
LicenseError license_error;
|
|
if (!license_error.ParseFromString(signed_message.msg())) {
|
|
LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error");
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
switch (license_error.error_code()) {
|
|
case LicenseError::INVALID_DRM_DEVICE_CERTIFICATE:
|
|
return NEED_PROVISIONING;
|
|
case LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE:
|
|
return DEVICE_REVOKED;
|
|
case LicenseError::SERVICE_UNAVAILABLE:
|
|
default:
|
|
LOGW("CdmLicense::HandleKeyErrorResponse: Unknown error type = %d",
|
|
license_error.error_code());
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
// Return the ClientIdentification message token type for license request.
|
|
// NOTE: an OEM Cert should never be presented to the provisioning server.
|
|
bool CdmLicense::GetClientTokenType(
|
|
ClientIdentification::TokenType* token_type) {
|
|
switch (client_token_type_) {
|
|
case kClientTokenKeybox:
|
|
*token_type = ClientIdentification::KEYBOX;
|
|
return true;
|
|
case kClientTokenDrmCert:
|
|
*token_type = ClientIdentification::DRM_DEVICE_CERTIFICATE;
|
|
return true;
|
|
case kClientTokenOemCert:
|
|
default:
|
|
// shouldn't happen
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareClientId(
|
|
const CdmAppParameterMap& app_parameters, LicenseRequest* license_request) {
|
|
ClientIdentification* client_id = license_request->mutable_client_id();
|
|
|
|
ClientIdentification::TokenType token_type;
|
|
if (!GetClientTokenType(&token_type)) {
|
|
return LICENSING_CLIENT_TOKEN_ERROR_1;
|
|
}
|
|
client_id->set_type(token_type);
|
|
client_id->set_token(client_token_);
|
|
|
|
ClientIdentification_NameValue* client_info;
|
|
CdmAppParameterMap::const_iterator iter;
|
|
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(iter->first);
|
|
client_info->set_value(iter->second);
|
|
}
|
|
std::string value;
|
|
if (Properties::GetCompanyName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kCompanyNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetModelName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kModelNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetArchitectureName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kArchitectureNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetDeviceName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetProductName(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kProductNameKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetBuildInfo(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kBuildInfoKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (crypto_session_->GetInternalDeviceUniqueId(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceIdKey);
|
|
client_info->set_value(value);
|
|
}
|
|
if (Properties::GetWVCdmVersion(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kWVCdmVersionKey);
|
|
client_info->set_value(value);
|
|
}
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kOemCryptoSecurityPatchLevelKey);
|
|
std::stringstream ss;
|
|
ss << (uint32_t)crypto_session_->GetSecurityPatchLevel();
|
|
client_info->set_value(ss.str());
|
|
|
|
ClientIdentification_ClientCapabilities* client_capabilities =
|
|
client_id->mutable_client_capabilities();
|
|
bool supports_usage_information;
|
|
if (crypto_session_->UsageInformationSupport(&supports_usage_information)) {
|
|
client_capabilities->set_session_token(supports_usage_information);
|
|
}
|
|
|
|
client_capabilities->set_anti_rollback_usage_table(
|
|
crypto_session_->IsAntiRollbackHwPresent());
|
|
|
|
uint32_t api_version = 0;
|
|
if (crypto_session_->GetApiVersion(&api_version)) {
|
|
client_capabilities->set_oem_crypto_api_version(api_version);
|
|
}
|
|
|
|
CryptoSession::HdcpCapability current_version, max_version;
|
|
if (crypto_session_->GetHdcpCapabilities(¤t_version, &max_version)) {
|
|
switch (max_version) {
|
|
case HDCP_NONE:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE);
|
|
break;
|
|
case HDCP_V1:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1);
|
|
break;
|
|
case HDCP_V2:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2);
|
|
break;
|
|
case HDCP_V2_1:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1);
|
|
break;
|
|
case HDCP_V2_2:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2);
|
|
break;
|
|
case HDCP_NO_DIGITAL_OUTPUT:
|
|
client_capabilities->set_max_hdcp_version(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NO_DIGITAL_OUTPUT);
|
|
break;
|
|
default:
|
|
LOGW(
|
|
"CdmLicense::PrepareClientId: unexpected HDCP max capability "
|
|
"version %d",
|
|
max_version);
|
|
}
|
|
}
|
|
|
|
CryptoSession::SupportedCertificateTypes supported_certs;
|
|
if (crypto_session_->GetSupportedCertificateTypes(&supported_certs)) {
|
|
if (supported_certs.rsa_2048_bit) {
|
|
client_capabilities->add_supported_certificate_key_type(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048);
|
|
}
|
|
if (supported_certs.rsa_3072_bit) {
|
|
client_capabilities->add_supported_certificate_key_type(
|
|
video_widevine::
|
|
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072);
|
|
}
|
|
}
|
|
|
|
client_capabilities->set_can_update_srm(
|
|
crypto_session_->IsSrmUpdateSupported());
|
|
uint16_t srm_version;
|
|
if (crypto_session_->GetSrmVersion(&srm_version))
|
|
client_capabilities->set_srm_version(srm_version);
|
|
|
|
if (service_certificate_->IsRequired()) {
|
|
if (!service_certificate_->IsAvailable()) {
|
|
LOGE("CdmLicense::PrepareClientId: Service Certificate not staged");
|
|
return LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR;
|
|
}
|
|
|
|
EncryptedClientIdentification* encrypted_client_id =
|
|
license_request->mutable_encrypted_client_id();
|
|
CdmResponseType status;
|
|
status = service_certificate_->EncryptClientId(client_id,
|
|
encrypted_client_id);
|
|
if (NO_ERROR == status) {
|
|
license_request->clear_client_id();
|
|
} else {
|
|
license_request->clear_encrypted_client_id();
|
|
}
|
|
return status;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::PrepareContentId(
|
|
const InitializationData& init_data, CdmLicenseType license_type,
|
|
const std::string& request_id, LicenseRequest* license_request) {
|
|
// Content Identification may be a cenc_id, a webm_id or a license_id
|
|
LicenseRequest_ContentIdentification* content_id =
|
|
license_request->mutable_content_id();
|
|
|
|
if (init_data.is_cenc() || init_data.is_hls()) {
|
|
LicenseRequest_ContentIdentification_CencDeprecated* cenc_content_id =
|
|
content_id->mutable_cenc_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
cenc_content_id->add_pssh(init_data.data());
|
|
} else {
|
|
LOGE("CdmLicense::PrepareKeyRequest: ISO-CENC init data not available");
|
|
return CENC_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, cenc_content_id)) {
|
|
return PREPARE_CENC_CONTENT_ID_FAILED;
|
|
}
|
|
} else if (init_data.is_webm()) {
|
|
LicenseRequest_ContentIdentification_WebmDeprecated* webm_content_id =
|
|
content_id->mutable_webm_id_deprecated();
|
|
|
|
if (!init_data.IsEmpty()) {
|
|
webm_content_id->set_header(init_data.data());
|
|
} else {
|
|
LOGE("CdmLicense::PrepareKeyRequest: WebM init data not available");
|
|
return WEBM_INIT_DATA_UNAVAILABLE;
|
|
}
|
|
|
|
if (!SetTypeAndId(license_type, request_id, webm_content_id)) {
|
|
return PREPARE_WEBM_CONTENT_ID_FAILED;
|
|
}
|
|
} else {
|
|
LOGE("CdmLicense::PrepareKeyRequest: no support for init data type (%s)",
|
|
init_data.type().c_str());
|
|
return UNSUPPORTED_INIT_DATA_FORMAT;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
template <typename T>
|
|
bool CdmLicense::SetTypeAndId(CdmLicenseType license_type,
|
|
const std::string& request_id,
|
|
T* content_id) {
|
|
switch (license_type) {
|
|
case kLicenseTypeOffline:
|
|
content_id->set_license_type(video_widevine::OFFLINE);
|
|
break;
|
|
case kLicenseTypeStreaming:
|
|
case kLicenseTypeTemporary:
|
|
content_id->set_license_type(video_widevine::STREAMING);
|
|
break;
|
|
default:
|
|
LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d",
|
|
license_type);
|
|
return false;
|
|
}
|
|
|
|
content_id->set_request_id(request_id);
|
|
return true;
|
|
}
|
|
|
|
} // namespace wvcdm
|