//////////////////////////////////////////////////////////////////////////////// // Copyright 2020 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// // Implementation of the SecurityProfileList class. #include "common/security_profile_list.h" #include #include "glog/logging.h" #include "google/protobuf/text_format.h" #include "common/client_id_util.h" #include "common/device_status_list.h" #include "protos/public/client_identification.pb.h" #include "protos/public/device_certificate_status.pb.h" #include "protos/public/device_common.pb.h" #include "protos/public/provisioned_device_info.pb.h" #include "protos/public/security_profile.pb.h" namespace widevine { using ClientCapabilities = ClientIdentification::ClientCapabilities; SecurityProfileList::SecurityProfileList(const std::string& profile_namespace) : profile_namespace_(profile_namespace) {} int SecurityProfileList::Init() { return 0; } int SecurityProfileList::GetQualifiedProfilesFromSpecifiedProfiles( const std::vector& profiles_to_check, const ClientIdentification& client_id, const ProvisionedDeviceInfo& device_info, std::vector* qualified_profiles) const { if (qualified_profiles == nullptr) { return 0; } qualified_profiles->clear(); absl::ReaderMutexLock lock(&mutex_); for (auto& profile_name : profiles_to_check) { SecurityProfile profile; if (GetProfileByName(profile_name, &profile)) { if (DoesProfileQualify(profile, client_id, device_info)) { qualified_profiles->push_back(profile.name()); } } } return qualified_profiles->size(); } int SecurityProfileList::GetQualifiedProfiles( const ClientIdentification& client_id, const ProvisionedDeviceInfo& device_info, std::vector* qualified_profiles) const { if (qualified_profiles == nullptr) { return 0; } qualified_profiles->clear(); absl::ReaderMutexLock lock(&mutex_); for (auto& profile : security_profiles_) { if (DoesProfileQualify(profile, client_id, device_info)) { qualified_profiles->push_back(profile.name()); } } return qualified_profiles->size(); } bool SecurityProfileList::GetDrmInfo(const ClientIdentification& client_id, const ProvisionedDeviceInfo& device_info, SecurityProfile::DrmInfo* drm_info) const { if (drm_info == nullptr) { return false; } drm_info->mutable_output()->set_hdcp_version( client_id.client_capabilities().max_hdcp_version()); drm_info->mutable_output()->set_analog_output_capabilities( client_id.client_capabilities().analog_output_capabilities()); drm_info->mutable_security()->set_oemcrypto_api_version( client_id.client_capabilities().oem_crypto_api_version()); drm_info->mutable_security()->set_resource_rating_tier( client_id.client_capabilities().resource_rating_tier()); drm_info->mutable_security()->set_security_level( device_info.security_level()); drm_info->mutable_request_model_info()->set_manufacturer( GetClientInfo(client_id, kModDrmMake)); drm_info->mutable_request_model_info()->set_model_name( GetClientInfo(client_id, kModDrmModel)); drm_info->mutable_request_model_info()->set_status( DeviceModel::MODEL_STATUS_UNVERIFIED); for (const auto& model_info : device_info.model_info()) { if (model_info.manufacturer() == drm_info->request_model_info().manufacturer() && model_info.model_name() == drm_info->request_model_info().model_name()) { drm_info->mutable_request_model_info()->set_status(model_info.status()); drm_info->mutable_request_model_info()->set_model_year( model_info.model_year()); break; } } drm_info->set_system_id(device_info.system_id()); SecurityProfile::ClientInfo* client_info = drm_info->mutable_client_info(); client_info->set_device_name(GetClientInfo(client_id, kModDrmDeviceName)); SecurityProfile::ClientInfo::ProductInfo* product_info = client_info->mutable_product_info(); product_info->set_product_name(GetClientInfo(client_id, kModDrmProductName)); product_info->set_build_info(GetClientInfo(client_id, kModDrmBuildInfo)); product_info->set_oem_crypto_security_patch_level( GetClientInfo(client_id, kModDrmOemCryptoSecurityPatchLevel)); // TODO(user): Figure out how to get device platform pushed into SPL API. DeviceCertificateStatus device_certificate_status; SecurityProfile::DeviceState device_model_state = SecurityProfile::DEVICE_STATE_UNKNOWN; Status status = DeviceStatusList::Instance()->GetDeviceCertificateStatusBySystemId( device_info.system_id(), &device_certificate_status); if (status.ok() && device_certificate_status.has_status()) { switch (device_certificate_status.status()) { case DeviceCertificateStatus::STATUS_IN_TESTING: device_model_state = SecurityProfile::IN_TESTING; break; case DeviceCertificateStatus::STATUS_RELEASED: device_model_state = SecurityProfile::RELEASED; break; case DeviceCertificateStatus::STATUS_TEST_ONLY: device_model_state = SecurityProfile::TEST_ONLY; break; case DeviceCertificateStatus::STATUS_REVOKED: device_model_state = SecurityProfile::REVOKED; break; default: break; } } drm_info->set_device_model_state(device_model_state); return true; } bool SecurityProfileList::GetProfileByName( const std::string& name, SecurityProfile* security_profile) const { absl::ReaderMutexLock lock(&mutex_); for (auto& profile : security_profiles_) { if (profile.name() == name) { if (security_profile != nullptr) { *security_profile = profile; } return true; } } return false; } bool SecurityProfileList::InsertProfile( const SecurityProfile& profile_to_insert) { // Check if profile already exist. if (GetProfileByName(profile_to_insert.name(), nullptr)) { LOG(ERROR) << "Unable to insert profile: " << profile_to_insert.name() << ". Name already exist."; return false; } if (profile_to_insert.min_security_requirements().security_level() == ProvisionedDeviceInfo::LEVEL_UNSPECIFIED) { LOG(ERROR) << "Unable to insert profile: " << profile_to_insert.name() << ". Security level not specified."; return false; } absl::WriterMutexLock lock(&mutex_); security_profiles_.push_back(profile_to_insert); return true; } int SecurityProfileList::NumProfiles() const { absl::ReaderMutexLock lock(&mutex_); return security_profiles_.size(); } void SecurityProfileList::ClearAllProfiles() { absl::WriterMutexLock lock(&mutex_); security_profiles_.clear(); } int64_t SecurityProfileList::GetCurrentTimeSeconds() const { return time(nullptr); } bool SecurityProfileList::DoesProfileQualify( const SecurityProfile& profile, const ClientIdentification& client_id, const ProvisionedDeviceInfo& device_info) const { // First filtering out inactive profiles (expired or future start time // profiles). if (!IsProfileActive(profile, GetCurrentTimeSeconds())) { return false; } // Check if the current device could be overridden. if (profile.device_exceptions_size() > 0) { uint32_t system_id = device_info.system_id(); for (const auto& device_exception : profile.device_exceptions()) { if (device_exception.system_id() == system_id) { if (device_exception.action() == DeviceException::DEVICE_EXCEPTION_ALLOW) { // Include this profile for the system Id regardless of other profile // requirements. return true; } else if (device_exception.action() == DeviceException::DEVICE_EXCEPTION_BLOCK) { // Do not use this profile for the system Id regardless of other // profile requirements. return false; } } } } if (profile.min_security_requirements().security_level() < device_info.security_level()) { VLOG(1) << "Profile does not qualify <" << profile.name() << "> security level: " << profile.min_security_requirements().security_level() << ", device: " << device_info.security_level(); return false; } if (profile.min_security_requirements().oemcrypto_api_version() > client_id.client_capabilities().oem_crypto_api_version()) { VLOG(1) << "Profile does not qualify <" << profile.name() << "> oemcrypto version: " << profile.min_security_requirements().oemcrypto_api_version() << ", device: " << client_id.client_capabilities().oem_crypto_api_version(); return false; } if (profile.min_output_requirements().hdcp_version() > client_id.client_capabilities().max_hdcp_version()) { VLOG(1) << "profile does not qualify <" << profile.name() << "> hdcp_version: " << profile.min_output_requirements().hdcp_version() << ", device: " << client_id.client_capabilities().max_hdcp_version(); return false; } if (profile.min_output_requirements().analog_output_capabilities() > client_id.client_capabilities().analog_output_capabilities()) { VLOG(1) << "Profile idoes not qualify <" << profile.name() << "> analog output: " << profile.min_output_requirements().analog_output_capabilities() << ", device: " << client_id.client_capabilities().analog_output_capabilities(); return false; } if (profile.min_security_requirements().resource_rating_tier() > client_id.client_capabilities().resource_rating_tier()) { VLOG(1) << "Profile does not qualify <" << profile.name() << "> resource rating tier: " << profile.min_security_requirements().resource_rating_tier() << ", device: " << client_id.client_capabilities().resource_rating_tier(); return false; } return true; } void SecurityProfileList::GetProfileNames( std::vector* profile_names) const { if (profile_names == nullptr) { return; } absl::ReaderMutexLock lock(&mutex_); for (auto& profile : security_profiles_) { profile_names->push_back(profile.name()); } } bool SecurityProfileList::IsProfileActive(const SecurityProfile& profile, int64_t current_time_seconds) const { if (!profile.has_control_time()) { return true; } // Filter out profile with start time in the future. if (profile.control_time().start_time_seconds() > current_time_seconds) { VLOG(1) << "Profile does not qualify <" << profile.name() << "> start time: " << profile.control_time().start_time_seconds() << ", current time: " << current_time_seconds << ". This is an inactive profile with a future start time."; return false; } int64_t end_time_seconds = profile.control_time().end_time_seconds(); // For never expired profile, it shouldn't be filtered out. if (end_time_seconds == 0) { return true; } // Filter out expired profile. if (end_time_seconds < current_time_seconds) { VLOG(1) << "Profile does not qualify <" << profile.name() << "> end time: " << end_time_seconds << ", current time: " << current_time_seconds << ". This is an inactive profile which has been expired."; return false; } return true; } } // namespace widevine