Files
provisioning_sdk_source/common/security_profile_list.cc
2020-09-21 15:54:27 -07:00

303 lines
12 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// 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 <vector>
#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<std::string>& profiles_to_check,
const ClientIdentification& client_id,
const ProvisionedDeviceInfo& device_info,
std::vector<std::string>* 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<std::string>* 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<std::string>* 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