// 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 #include #include #include "log.h" #include "media_cas.pb.h" namespace wvcas { KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) { return keys_[static_cast(slot_id)]; } const KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) const { return keys_[static_cast(slot_id)]; } WidevineCasSession::~WidevineCasSession() { if (crypto_session_ != nullptr) { crypto_session_->RemoveEntitledKeySession(key_session_id_); } } CasStatus WidevineCasSession::initialize( std::shared_ptr crypto_session, CasEventListener* event_listener, uint32_t* session_id) { std::unique_lock 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 crypto_session) { std::unique_lock 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 lock(crypto_lock_); if (ecm != current_ecm_) { LOGD("WidevineCasSession::processEcm: received new ecm"); std::unique_ptr 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 message; if (!ecm_parser->fingerprinting().control().empty()) { message.push_back(static_cast( 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(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 WidevineCasSession::getEcmParser( const CasEcm& ecm) const { return EcmParser::Create(ecm); } const char* WidevineCasSession::securityLevel() { return crypto_session_->SecurityLevel(); } } // namespace wvcas