303 lines
12 KiB
C++
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
|