Includes fixes for provisioning and license renewal signature generation. bug: 8620943 Merge of: https://widevine-internal-review.googlesource.com/#/c/5231/ https://widevine-internal-review.googlesource.com/#/c/5200/ from the Widevine CDM repository. Change-Id: I2928c9d59ad5337ca34b4ef7ed58272d34755d2d
441 lines
14 KiB
C++
441 lines
14 KiB
C++
// Copyright 2012 Google Inc. All Rights Reserved.
|
|
|
|
#include "license.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "crypto_engine.h"
|
|
#include "crypto_session.h"
|
|
#include "log.h"
|
|
#include "policy_engine.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
|
|
namespace {
|
|
std::string kCompanyNameKey = "company_name";
|
|
std::string kCompanyNameValueGoogle = "Google";
|
|
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";
|
|
}
|
|
|
|
namespace wvcdm {
|
|
|
|
// Protobuf generated classes.
|
|
using video_widevine_server::sdk::ClientIdentification;
|
|
using video_widevine_server::sdk::ClientIdentification_NameValue;
|
|
using video_widevine_server::sdk::LicenseRequest;
|
|
using video_widevine_server::sdk::LicenseRequest_ContentIdentification;
|
|
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC;
|
|
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_ExistingLicense;
|
|
using video_widevine_server::sdk::License;
|
|
using video_widevine_server::sdk::License_KeyContainer;
|
|
using video_widevine_server::sdk::LicenseError;
|
|
using video_widevine_server::sdk::SignedMessage;
|
|
using video_widevine_server::sdk::STREAMING;
|
|
using video_widevine_server::sdk::VERSION_2_1;
|
|
|
|
|
|
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) {
|
|
// TODO(fredgc): Figure out what key.type is for Generic Keys.
|
|
// If the generic signing key is CONTENT, then the extra size log below is good.
|
|
// If it is SIGNING, then we are ignoring it. -- we should fix that by adding
|
|
// an else clause to this if statement.
|
|
if (license.key(i).type() == License_KeyContainer::CONTENT) {
|
|
CryptoKey key;
|
|
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.
|
|
size_t length = license.key(i).key().size() - 16;
|
|
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());
|
|
}
|
|
|
|
key_array.push_back(key);
|
|
}
|
|
}
|
|
|
|
return key_array;
|
|
}
|
|
|
|
CdmLicense::CdmLicense(): session_(NULL) {}
|
|
|
|
CdmLicense::~CdmLicense() {}
|
|
|
|
bool CdmLicense::Init(const std::string& token,
|
|
CryptoSession* session,
|
|
PolicyEngine* policy_engine) {
|
|
if (token.size() == 0)
|
|
return false;
|
|
if (session == NULL || !session->IsValid() || !session->IsOpen())
|
|
return false;
|
|
token_ = token;
|
|
session_ = session;
|
|
policy_engine_ = policy_engine;
|
|
return true;
|
|
}
|
|
|
|
bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
|
const CdmLicenseType license_type,
|
|
CdmAppParameterMap& app_parameters,
|
|
CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!session_ ||
|
|
token_.empty()) {
|
|
return false;
|
|
}
|
|
if (init_data.empty()) {
|
|
LOGE("CdmLicense::PrepareKeyRequest : No init data provided;");
|
|
return false;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyRequest : No signed request provided.");
|
|
return false;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyRequest : No server url provided.");
|
|
return false;
|
|
}
|
|
|
|
// TODO(gmorgan): Request ID owned by session?
|
|
std::string request_id;
|
|
session_->GenerateRequestId(request_id);
|
|
|
|
LicenseRequest license_request;
|
|
ClientIdentification* client_id = license_request.mutable_client_id();
|
|
|
|
if (Properties::use_certificates_as_identification())
|
|
client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE);
|
|
else
|
|
client_id->set_type(ClientIdentification::KEYBOX);
|
|
client_id->set_token(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;
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kCompanyNameKey);
|
|
client_info->set_value(kCompanyNameValueGoogle);
|
|
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 (CryptoEngine::GetInstance()->GetDeviceUniqueId(&value)) {
|
|
client_info = client_id->add_client_info();
|
|
client_info->set_name(kDeviceIdKey);
|
|
client_info->set_value(value);
|
|
}
|
|
|
|
// Content Identification may be a cenc_id, a webm_id or a license_id
|
|
LicenseRequest_ContentIdentification* content_id =
|
|
license_request.mutable_content_id();
|
|
|
|
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
|
|
content_id->mutable_cenc_id();
|
|
cenc_content_id->add_pssh(init_data);
|
|
cenc_content_id->set_license_type(STREAMING);
|
|
cenc_content_id->set_request_id(request_id);
|
|
|
|
// TODO(jfore): The time field will be updated once the cdm wrapper
|
|
// has been updated to pass us in the time.
|
|
license_request.set_request_time(0);
|
|
|
|
license_request.set_type(LicenseRequest::NEW);
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!session_->GenerateNonce(&nonce)) {
|
|
return false;
|
|
}
|
|
license_request.set_key_control_nonce(UintToString(nonce));
|
|
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
|
license_request.set_protocol_version(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 (!session_->PrepareRequest(serialized_license_req,
|
|
&license_request_signature, false)) {
|
|
signed_request->clear();
|
|
return false;
|
|
}
|
|
|
|
if (license_request_signature.empty()) {
|
|
signed_request->clear();
|
|
return false;
|
|
}
|
|
|
|
// 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 true;
|
|
}
|
|
|
|
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request,
|
|
std::string* server_url) {
|
|
if (!session_) {
|
|
return false;
|
|
}
|
|
if (!signed_request) {
|
|
LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided.");
|
|
return false;
|
|
}
|
|
if (!server_url) {
|
|
LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided.");
|
|
return false;
|
|
}
|
|
|
|
LicenseRequest license_request;
|
|
license_request.set_type(LicenseRequest::RENEWAL);
|
|
|
|
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
|
license_request.mutable_content_id()->mutable_license();
|
|
current_license->mutable_license_id()->CopyFrom(license_id_);
|
|
|
|
// Get/set the nonce. This value will be reflected in the Key Control Block
|
|
// of the license response.
|
|
uint32_t nonce;
|
|
if (!session_->GenerateNonce(&nonce)) {
|
|
return false;
|
|
}
|
|
license_request.set_key_control_nonce(UintToString(nonce));
|
|
LOGD("PrepareKeyRenewalRequest: nonce=%u", nonce);
|
|
license_request.set_protocol_version(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 (!session_->PrepareRenewalRequest(serialized_license_req,
|
|
&license_request_signature))
|
|
return false;
|
|
|
|
if (license_request_signature.empty()) return false;
|
|
|
|
// 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 true;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyResponse(
|
|
const CdmKeyResponse& license_response) {
|
|
if (!session_) {
|
|
return KEY_ERROR;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyResponse : Empty license response.");
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response))
|
|
return KEY_ERROR;
|
|
|
|
if (signed_response.type() == SignedMessage::ERROR) {
|
|
return HandleKeyErrorResponse(signed_response);
|
|
}
|
|
|
|
if (!signed_response.has_signature())
|
|
return KEY_ERROR;
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg()))
|
|
return KEY_ERROR;
|
|
|
|
if (Properties::use_certificates_as_identification()) {
|
|
if (!signed_response.has_session_key())
|
|
return KEY_ERROR;
|
|
|
|
if (!session_->GenerateDerivedKeys(key_request_,
|
|
signed_response.session_key()))
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
// Extract mac key
|
|
std::string mac_key_iv;
|
|
std::string mac_key;
|
|
if (license.policy().can_renew())
|
|
{
|
|
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 (mac_key_iv.size() != KEY_IV_SIZE ||
|
|
mac_key.size() != MAC_KEY_SIZE) {
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
// License Id should not be empty for renewable license
|
|
if (!license.has_id()) return KEY_ERROR;
|
|
|
|
license_id_.CopyFrom(license.id());
|
|
}
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
if (!key_array.size()) {
|
|
LOGE("CdmLicense::HandleKeyResponse : No content keys.");
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
if (license.policy().has_renewal_server_url()) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
policy_engine_->SetLicense(license);
|
|
|
|
if (session_->LoadKeys(signed_response.msg(),
|
|
signed_response.signature(),
|
|
mac_key_iv,
|
|
mac_key,
|
|
key_array.size(),
|
|
&key_array[0])) {
|
|
return KEY_ADDED;
|
|
}
|
|
else {
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyRenewalResponse(
|
|
const CdmKeyResponse& license_response) {
|
|
if (!session_) {
|
|
return KEY_ERROR;
|
|
}
|
|
if (license_response.empty()) {
|
|
LOGE("CdmLicense::HandleKeyRenewalResponse : Empty license response.");
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
SignedMessage signed_response;
|
|
if (!signed_response.ParseFromString(license_response))
|
|
return KEY_ERROR;
|
|
|
|
if (signed_response.type() == SignedMessage::ERROR) {
|
|
return HandleKeyErrorResponse(signed_response);
|
|
}
|
|
|
|
if (!signed_response.has_signature())
|
|
return KEY_ERROR;
|
|
|
|
License license;
|
|
if (!license.ParseFromString(signed_response.msg()))
|
|
return KEY_ERROR;
|
|
|
|
if (!license.has_id()) return KEY_ERROR;
|
|
|
|
if (license.id().version() > license_id_.version()) {
|
|
// This is the normal case.
|
|
license_id_.CopyFrom(license.id());
|
|
|
|
if (license.policy().has_renewal_server_url() &&
|
|
license.policy().renewal_server_url().size() > 0) {
|
|
server_url_ = license.policy().renewal_server_url();
|
|
}
|
|
|
|
policy_engine_->UpdateLicense(license);
|
|
|
|
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
|
|
|
if (session_->RefreshKeys(signed_response.msg(),
|
|
signed_response.signature(),
|
|
key_array.size(),
|
|
&key_array[0])) {
|
|
return KEY_ADDED;
|
|
}
|
|
else {
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
// This isn't supposed to happen.
|
|
// TODO(jfore): Handle wrap? We can miss responses and that should be
|
|
// considered normal until retries are exhausted.
|
|
return KEY_ERROR;
|
|
}
|
|
|
|
CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
|
const SignedMessage& signed_message) {
|
|
|
|
LicenseError license_error;
|
|
if (!license_error.ParseFromString(signed_message.msg()))
|
|
return KEY_ERROR;
|
|
|
|
switch (license_error.error_code()) {
|
|
case LicenseError::INVALID_CREDENTIALS:
|
|
return NEED_PROVISIONING;
|
|
case LicenseError::REVOKED_CREDENTIALS:
|
|
return DEVICE_REVOKED;
|
|
case LicenseError::SERVICE_UNAVAILABLE:
|
|
default:
|
|
return KEY_ERROR;
|
|
}
|
|
}
|
|
|
|
} // namespace wvcdm
|