-Parse EMM in Cas plugin -Entitlement key rotation support -Multi_content_license support
189 lines
6.9 KiB
C++
189 lines
6.9 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 "widevine_cas_session.h"
|
|
|
|
#include <cas_events.h>
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#include "log.h"
|
|
#include "media_cas.pb.h"
|
|
|
|
namespace wvcas {
|
|
|
|
KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) {
|
|
return keys_[static_cast<int>(slot_id)];
|
|
}
|
|
|
|
const KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) const {
|
|
return keys_[static_cast<int>(slot_id)];
|
|
}
|
|
|
|
WidevineCasSession::~WidevineCasSession() {
|
|
if (crypto_session_ != nullptr) {
|
|
crypto_session_->RemoveEntitledKeySession(key_session_id_);
|
|
}
|
|
}
|
|
|
|
CasStatus WidevineCasSession::initialize(
|
|
std::shared_ptr<CryptoSession> crypto_session,
|
|
CasEventListener* event_listener, uint32_t* session_id) {
|
|
std::unique_lock<std::mutex> lock(crypto_lock_);
|
|
if (crypto_session == nullptr || session_id == nullptr) {
|
|
LOGE("WidevineCasSession::initialize: missing input parameters");
|
|
return CasStatus(CasStatusCode::kInvalidParameter,
|
|
"missing input parameters");
|
|
}
|
|
crypto_session_ = std::move(crypto_session);
|
|
crypto_session_->CreateEntitledKeySession(&key_session_id_);
|
|
*session_id = key_session_id_;
|
|
event_listener_ = event_listener;
|
|
return CasStatusCode::kNoError;
|
|
}
|
|
|
|
CasStatus WidevineCasSession::resetCryptoSession(
|
|
std::shared_ptr<CryptoSession> crypto_session) {
|
|
std::unique_lock<std::mutex> lock(crypto_lock_);
|
|
if (crypto_session == nullptr) {
|
|
return CasStatus(CasStatusCode::kInvalidParameter,
|
|
"Can not reset crypto session to null");
|
|
}
|
|
crypto_session_ = std::move(crypto_session);
|
|
return crypto_session_->ReassociateEntitledKeySession(key_session_id_);
|
|
}
|
|
|
|
CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
|
uint8_t parental_control_age,
|
|
const std::string& license_group_id) {
|
|
std::unique_lock<std::mutex> lock(crypto_lock_);
|
|
if (ecm != current_ecm_) {
|
|
LOGD("WidevineCasSession::processEcm: received new ecm");
|
|
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
|
|
if (ecm_parser == nullptr) {
|
|
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
|
|
}
|
|
if (!license_group_id.empty() &&
|
|
!ecm_parser->set_group_id(license_group_id)) {
|
|
return CasStatus(CasStatusCode::kInvalidParameter, "invalid group id");
|
|
}
|
|
|
|
ecm_age_restriction_ = ecm_parser->age_restriction();
|
|
// Parental control check.
|
|
if (parental_control_age > 0 &&
|
|
parental_control_age < ecm_age_restriction_) {
|
|
const std::string message(1, parental_control_age);
|
|
return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message);
|
|
}
|
|
|
|
std::vector<uint8_t> message;
|
|
if (!ecm_parser->fingerprinting().control().empty()) {
|
|
message.push_back(static_cast<uint8_t>(
|
|
SessionFingerprintingFieldType::SESSION_FINGERPRINTING_CONTROL));
|
|
const std::string control = ecm_parser->fingerprinting().control();
|
|
message.push_back((control.size() >> 8) & 0xff);
|
|
message.push_back(control.size() & 0xff);
|
|
message.insert(message.end(), control.begin(), control.end());
|
|
}
|
|
if (message != last_fingerprinting_message_) {
|
|
last_fingerprinting_message_ = message;
|
|
if (event_listener_ == nullptr) {
|
|
LOGW("event_listener is null. Fingerprinting info ignored!");
|
|
} else {
|
|
event_listener_->OnSessionFingerprintingUpdated(key_session_id_,
|
|
message);
|
|
}
|
|
}
|
|
|
|
message.clear();
|
|
for (int i = 0; i < ecm_parser->service_blocking().device_groups_size();
|
|
++i) {
|
|
message.push_back(
|
|
static_cast<uint8_t>(SessionServiceBlockingFieldType::
|
|
SESSION_SERVICE_BLOCKING_DEVICE_GROUP));
|
|
const std::string device_group =
|
|
ecm_parser->service_blocking().device_groups(i);
|
|
message.push_back((device_group.size() >> 8) & 0xff);
|
|
message.push_back(device_group.size() & 0xff);
|
|
message.insert(message.end(), device_group.begin(), device_group.end());
|
|
}
|
|
if (message != last_service_blocking_message_) {
|
|
last_service_blocking_message_ = message;
|
|
if (event_listener_ == nullptr) {
|
|
LOGW("event_listener is null. Service blocking info ignored!");
|
|
} else {
|
|
event_listener_->OnSessionServiceBlockingUpdated(key_session_id_,
|
|
message);
|
|
}
|
|
}
|
|
|
|
entitlement_period_index_ = ecm_parser->entitlement_period_index();
|
|
entitlement_rotation_window_left_ =
|
|
ecm_parser->entitlement_rotation_window_left();
|
|
|
|
bool load_even = false;
|
|
bool load_odd = false;
|
|
KeySlotId keyslot_id = KeySlotId::kEvenKeySlot;
|
|
// Temporary key slots to only have successfully loaded keys in |keys_|.
|
|
CasKeySlotData keys;
|
|
do {
|
|
if (keys_[keyslot_id].wrapped_key !=
|
|
ecm_parser->wrapped_key_data(keyslot_id)) {
|
|
KeySlot& key = keys[keyslot_id];
|
|
key.key_id = ecm_parser->content_key_id(keyslot_id);
|
|
key.wrapped_key = ecm_parser->wrapped_key_data(keyslot_id);
|
|
key.wrapped_key_iv = ecm_parser->wrapped_key_iv(keyslot_id);
|
|
key.entitlement_key_id = ecm_parser->entitlement_key_id(keyslot_id);
|
|
key.cipher_mode = ecm_parser->crypto_mode();
|
|
key.content_iv = ecm_parser->content_iv(keyslot_id);
|
|
if (keyslot_id == KeySlotId::kEvenKeySlot) {
|
|
load_even = true;
|
|
} else {
|
|
load_odd = true;
|
|
}
|
|
if (key.content_iv.size() == 8) {
|
|
key.content_iv.resize(16, 0);
|
|
}
|
|
}
|
|
if (!ecm_parser->rotation_enabled() ||
|
|
keyslot_id == KeySlotId::kOddKeySlot) {
|
|
break;
|
|
}
|
|
keyslot_id = KeySlotId::kOddKeySlot;
|
|
} while (true);
|
|
if (load_even || load_odd) {
|
|
CasStatus status = crypto_session_->LoadCasECMKeys(
|
|
key_session_id_,
|
|
(load_even ? &keys[KeySlotId::kEvenKeySlot] : nullptr),
|
|
(load_odd ? &keys[KeySlotId::kOddKeySlot] : nullptr));
|
|
if (status.status_code() != CasStatusCode::kNoError) {
|
|
LOGE("WidevineCasSession::processEcm: error %d, msg %s",
|
|
status.status_code(), status.error_string().c_str());
|
|
return status;
|
|
}
|
|
|
|
// Don't update on failure, to not to lose still working key_id.
|
|
if (load_even) {
|
|
keys_[KeySlotId::kEvenKeySlot] = keys[KeySlotId::kEvenKeySlot];
|
|
}
|
|
if (load_odd) {
|
|
keys_[KeySlotId::kOddKeySlot] = keys[KeySlotId::kOddKeySlot];
|
|
}
|
|
}
|
|
current_ecm_ = ecm;
|
|
}
|
|
return CasStatusCode::kNoError;
|
|
}
|
|
|
|
std::unique_ptr<EcmParser> WidevineCasSession::getEcmParser(
|
|
const CasEcm& ecm) const {
|
|
return EcmParser::Create(ecm);
|
|
}
|
|
|
|
const char* WidevineCasSession::securityLevel() {
|
|
return crypto_session_->SecurityLevel();
|
|
}
|
|
|
|
} // namespace wvcas
|