Files
media_cas_client/plugin/src/widevine_cas_session.cpp
huihli 065ca035c9 Cas Client repo update-2.
-Parse EMM in Cas plugin
-Entitlement key rotation support
-Multi_content_license support
2021-06-28 11:34:11 -07:00

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