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

601 lines
24 KiB
C++

////////////////////////////////////////////////////////////////////////////////
// Copyright 2017 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.
////////////////////////////////////////////////////////////////////////////////
// Implements the DeviceStatusList class.
#include "common/device_status_list.h"
#include <time.h>
#include <map>
#include <memory>
#include <cstdint>
#include "glog/logging.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "util/gtl/map_util.h"
#include "common/client_cert.h"
#include "common/drm_service_certificate.h"
#include "common/error_space.h"
#include "common/hash_algorithm_util.h"
#include "common/keybox_client_cert.h"
#include "common/rsa_key.h"
#include "common/status.h"
#include "protos/public/client_identification.pb.h"
#include "protos/public/device_certificate_status.pb.h"
#include "protos/public/errors.pb.h"
#include "protos/public/signed_device_info.pb.h"
using ::widevine::DeviceCertificateStatusListRequest;
using ::widevine::SignedDeviceInfo;
using ::widevine::SignedDeviceInfoRequest;
namespace widevine {
namespace {
const char kSignedListTerminator[] = "}";
const char kSignedList[] = "signedList\":";
const std::size_t kSignedListLen = strlen(kSignedList);
} // namespace
DeviceStatusList* DeviceStatusList::Instance() {
// TODO(user): This is "ok" according to Google's Coding for Dummies, but
// we should inject the status list into the sessions. This will require
// exposing additional objects in the public interface.
static DeviceStatusList* device_status_list(nullptr);
if (!device_status_list) device_status_list = new DeviceStatusList;
return device_status_list;
}
DeviceStatusList::DeviceStatusList() {}
DeviceStatusList::~DeviceStatusList() {}
Status DeviceStatusList::UpdateStatusList(
const std::string& root_certificate_public_key,
const std::string& serialized_device_certificate_status_list,
HashAlgorithm hash_algorithm, const std::string& signature,
uint32_t expiration_period_seconds) {
if (serialized_device_certificate_status_list.empty()) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"missing-status-list");
}
if (signature.empty()) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"missing-status-list-signature");
}
std::unique_ptr<RsaPublicKey> root_key(
RsaPublicKey::Create(root_certificate_public_key));
if (root_key == nullptr) {
return Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-root-public-key");
}
if (!root_key->VerifySignature(serialized_device_certificate_status_list,
hash_algorithm, signature)) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"invalid-status-list-signature");
}
DeviceCertificateStatusList certificate_status_list;
if (!certificate_status_list.ParseFromString(
serialized_device_certificate_status_list)) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"signed-certificate-status-list-parse-error");
}
if (expiration_period_seconds &&
(GetCurrentTime() > (certificate_status_list.creation_time_seconds() +
expiration_period_seconds))) {
return Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST,
"certificate-status-list-expired");
}
absl::WriterMutexLock lock(&status_map_lock_);
device_status_map_.clear();
for (int i = 0, n = certificate_status_list.certificate_status_size(); i < n;
i++) {
const DeviceCertificateStatus& cert_status =
certificate_status_list.certificate_status(i);
if (cert_status.has_device_info()) {
const ProvisionedDeviceInfo& device_info = cert_status.device_info();
if (device_info.has_system_id()) {
device_status_map_[device_info.system_id()] = cert_status;
} else {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"device-info-missing-system-id");
}
}
}
creation_time_seconds_ = certificate_status_list.creation_time_seconds();
expiration_period_seconds_ = expiration_period_seconds;
return OkStatus();
}
Status DeviceStatusList::GetCertStatus(
const ClientCert& client_cert, const std::string& make,
const std::string& provider, bool allow_revoked_system_id,
DeviceCertificateStatus* device_certificate_status) {
CHECK(device_certificate_status);
absl::ReaderMutexLock lock(&status_map_lock_);
if (expiration_period_seconds_ &&
(GetCurrentTime() >
(creation_time_seconds_ + expiration_period_seconds_))) {
return Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST,
"certificate-status-list-expired");
}
DeviceCertificateStatus* device_cert_status =
gtl::FindOrNull(device_status_map_, client_cert.system_id());
if (device_cert_status == nullptr) {
if (allow_unknown_devices_ ||
client_cert.type() == ClientIdentification::KEYBOX) {
return OkStatus();
}
return client_cert.SystemIdUnknownError();
}
*device_certificate_status = *device_cert_status;
if (device_cert_status->status() == DeviceCertificateStatus::STATUS_REVOKED) {
if (IsRevokedSystemIdAllowed(client_cert.system_id()) ||
allow_revoked_system_id) {
LOG(WARNING) << "Allowing REVOKED device: "
<< device_cert_status->device_info().ShortDebugString();
} else {
return client_cert.SystemIdRevokedError();
}
}
// The remainder of this function is for DRM certificates.
if (client_cert.type() == ClientIdentification::KEYBOX) {
return OkStatus();
}
// DRM certificate checks.
if (client_cert.type() != ClientIdentification::DRM_DEVICE_CERTIFICATE) {
return Status(error_space, INVALID_DRM_CERTIFICATE,
"device-certificate-unsupported-token-type");
}
if ((device_cert_status->status() ==
DeviceCertificateStatus::STATUS_TEST_ONLY) &&
!allow_test_only_devices_) {
if (IsTestOnlyDeviceAllowedByMake(client_cert.system_id(), make) &&
IsTestOnlyDeviceAllowedByProvider(client_cert.system_id(), provider)) {
LOG(WARNING) << "Allowing TEST_ONLY device with systemId = "
<< client_cert.system_id() << ", make = " << make
<< ", provider = " << provider << ", device info = "
<< device_cert_status->device_info().ShortDebugString();
} else {
VLOG(2) << "Not allowing TEST ONLY device with systemId = "
<< client_cert.system_id() << ", provider = " << provider
<< ", device info = "
<< device_cert_status->device_info().ShortDebugString();
return Status(error_space, DEVELOPMENT_CERTIFICATE_NOT_ALLOWED,
"test-only-drm-certificate-not-allowed");
}
}
if (!client_cert.signed_by_provisioner() &&
(client_cert.signer_serial_number() !=
device_cert_status->drm_serial_number())) {
// Widevine-provisioned device, and the intermediate certificate serial
// number does not match that in the status list. If the status list is
// newer than the certificate, indicate an invalid certificate, so that
// the device re-provisions. If, on the other hand, the certificate status
// list is older than the certificate, the certificate is for all purposes
// unknown.
if (client_cert.signer_creation_time_seconds() < creation_time_seconds_) {
return Status(error_space, INVALID_DRM_CERTIFICATE,
"intermediate-certificate-serial-number-mismatch");
}
return client_cert.SystemIdUnknownError();
}
return OkStatus();
}
bool DeviceStatusList::GetDeviceInfo(const ClientCert& client_cert,
ProvisionedDeviceInfo* device_info) {
CHECK(device_info);
absl::ReaderMutexLock lock(&status_map_lock_);
DeviceCertificateStatus* device_cert_status =
gtl::FindOrNull(device_status_map_, client_cert.system_id());
if (device_cert_status != nullptr) {
*device_info = device_cert_status->device_info();
return true;
}
return false;
}
bool DeviceStatusList::GetRevokedIdentifiers(
uint32_t system_id,
DeviceCertificateStatus::RevokedIdentifiers* revoked_identifiers) {
CHECK(revoked_identifiers);
absl::ReaderMutexLock lock(&status_map_lock_);
DeviceCertificateStatus* device_cert_status =
gtl::FindOrNull(device_status_map_, system_id);
if (device_cert_status) {
*revoked_identifiers = device_cert_status->revoked_identifiers();
return true;
}
return false;
}
bool DeviceStatusList::IsSystemIdActive(uint32_t system_id) {
absl::ReaderMutexLock lock(&status_map_lock_);
DeviceCertificateStatus* device_cert_status =
gtl::FindOrNull(device_status_map_, system_id);
if (!device_cert_status) {
return allow_unknown_devices_ ||
KeyboxClientCert::IsSystemIdKnown(system_id);
}
if (device_cert_status->status() ==
DeviceCertificateStatus::STATUS_TEST_ONLY) {
return allow_test_only_devices_;
}
if (device_cert_status) {
ProvisionedDeviceInfo device_info = device_cert_status->device_info();
if (device_cert_status->status() ==
DeviceCertificateStatus::STATUS_REVOKED) {
if (IsRevokedSystemIdAllowed(system_id)) {
LOG(WARNING) << "REVOKED system_id: " << system_id
<< " is allowed to be active";
return true;
}
}
}
return device_cert_status->status() !=
DeviceCertificateStatus::STATUS_REVOKED;
}
uint32_t DeviceStatusList::GetCurrentTime() const { return time(nullptr); }
void DeviceStatusList::AllowRevokedDevices(const std::string& system_id_list) {
for (absl::string_view sp : absl::StrSplit(system_id_list, ',')) {
allowed_revoked_devices_.push_back(std::stoi(std::string(sp)));
}
std::sort(allowed_revoked_devices_.begin(), allowed_revoked_devices_.end());
}
void DeviceStatusList::AllowTestOnlyDevicesByMake(
const std::string& device_list_by_make) {
absl::WriterMutexLock lock(&allowed_test_only_devices_mutex_);
if (device_list_by_make.empty()) {
allowed_test_only_devices_by_make_.clear();
return;
}
for (absl::string_view device : absl::StrSplit(device_list_by_make, ',')) {
const std::pair<absl::string_view, absl::string_view> device_split =
absl::StrSplit(device, ':');
if (device_split.second.empty() || device_split.second == "*") {
allowed_test_only_devices_by_make_.emplace(
std::stoi(std::string(device_split.first)), "*");
VLOG(2) << "Allowing TEST_ONLY device: systemId = "
<< std::stoi(std::string(device_split.first)) << ", make *";
} else {
allowed_test_only_devices_by_make_.emplace(
std::stoi(std::string(device_split.first)),
absl::AsciiStrToUpper(device_split.second));
VLOG(2) << "Allowing TEST_ONLY device: systemId = "
<< std::stoi(std::string(device_split.first))
<< ", make = " << absl::AsciiStrToUpper(device_split.second);
}
}
}
void DeviceStatusList::AllowTestOnlyDevicesByProvider(
const std::string& device_list_by_provider) {
absl::WriterMutexLock lock(&allowed_test_only_devices_mutex_);
if (device_list_by_provider.empty()) {
allowed_test_only_devices_by_provider_.clear();
return;
}
for (absl::string_view device :
absl::StrSplit(device_list_by_provider, ',')) {
const std::pair<absl::string_view, absl::string_view> device_split =
absl::StrSplit(device, ':');
if (device_split.second.empty() || device_split.second == "*") {
allowed_test_only_devices_by_provider_.emplace(
std::stoi(std::string(device_split.first)), "*");
VLOG(2) << "Allowing TEST_ONLY device: systemId = "
<< std::stoi(std::string(device_split.first)) << ", provider *";
} else {
allowed_test_only_devices_by_provider_.emplace(
std::stoi(std::string(device_split.first)),
absl::AsciiStrToUpper(device_split.second));
VLOG(2) << "Allowing TEST_ONLY device: systemId = "
<< std::stoi(std::string(device_split.first))
<< ", provider = " << absl::AsciiStrToUpper(device_split.second);
}
}
}
bool DeviceStatusList::IsRevokedSystemIdAllowed(uint32_t system_id) {
auto it = std::binary_search(allowed_revoked_devices_.begin(),
allowed_revoked_devices_.end(), system_id);
return it;
}
bool DeviceStatusList::IsTestOnlyDeviceAllowedByMake(
uint32_t system_id, const std::string& manufacturer) {
absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_);
std::pair<std::multimap<uint32_t, std::string>::iterator,
std::multimap<uint32_t, std::string>::iterator>
allowed_makes = allowed_test_only_devices_by_make_.equal_range(system_id);
for (auto it = allowed_makes.first; it != allowed_makes.second; ++it) {
std::string allowed_makes = (*it).second;
if (allowed_makes == "*" ||
allowed_makes == absl::AsciiStrToUpper(manufacturer)) {
return true;
}
}
return false;
}
bool DeviceStatusList::IsTestOnlyDeviceAllowedByProvider(
uint32_t system_id, const std::string& provider) {
absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_);
std::pair<std::multimap<uint32_t, std::string>::iterator,
std::multimap<uint32_t, std::string>::iterator>
allowed_providers =
allowed_test_only_devices_by_provider_.equal_range(system_id);
for (auto it = allowed_providers.first; it != allowed_providers.second;
++it) {
std::string allowed_provider = (*it).second;
if (allowed_provider == "*" ||
allowed_provider == absl::AsciiStrToUpper(provider)) {
return true;
}
}
return false;
}
Status DeviceStatusList::DetermineAndDeserializeServiceResponse(
const std::string& service_response,
DeviceCertificateStatusList* certificate_status_list,
std::string* serialized_certificate_status_list,
HashAlgorithm* hash_algorithm, std::string* signature) {
if (certificate_status_list == nullptr) {
return Status(error_space, error::INVALID_ARGUMENT,
"certificate_status_list is empty");
} else if (serialized_certificate_status_list == nullptr) {
return Status(error_space, error::INVALID_ARGUMENT,
"serialized_certificate_status_list is empty");
} else if (signature == nullptr) {
return Status(error_space, error::INVALID_ARGUMENT, "signature is empty");
}
// We support three types of payload parsing. The legacy path checks for a
// JSON encoded payload as well as just a plain base64 (web safe or normal)
// payload. If that doesn't match, then the method will try to parse the
// serialized PublishedDeviceInfo proto.
Status status = ExtractPublishedDevicesInfo(
service_response, serialized_certificate_status_list, hash_algorithm,
signature);
// If the payload was not correctly parsed as a PublishedDevices proto.
// then attempt to parse it as a legacy payload.
if (!status.ok()) {
status = ExtractLegacyDeviceList(service_response,
serialized_certificate_status_list,
hash_algorithm, signature);
// The payload could not be parsed in either format, return the failure
// information.
if (!status.ok()) {
return status;
}
}
if (!certificate_status_list->ParseFromString(
*serialized_certificate_status_list)) {
return Status(error_space, widevine::INVALID_CERTIFICATE_STATUS_LIST,
"certificate-status-list-parse-error");
}
return OkStatus();
}
Status DeviceStatusList::ExtractLegacyDeviceList(
const std::string& raw_certificate_provisioning_service_response,
std::string* serialized_certificate_status_list,
HashAlgorithm* hash_algorithm, std::string* signature) {
// First, attempt to extract the legacy JSON response. Example legacy format.
// "signedList":"<b64 encoded data>"
// where the b64 encoded data is a DeviceCertificateStatusListResponse.
size_t b64_list_response_start =
raw_certificate_provisioning_service_response.find(kSignedList);
std::string serialized_signed_certificate_status_list;
if (b64_list_response_start != std::string::npos) {
size_t b64_list_response_end =
raw_certificate_provisioning_service_response.find(
kSignedListTerminator, b64_list_response_start);
if (b64_list_response_end == std::string::npos) {
return Status(
error_space, error::INVALID_ARGUMENT,
"Unable to parse the certificate_provisioning_service_response. "
"SignedList not terminated.");
}
std::string signed_list(
raw_certificate_provisioning_service_response.begin() +
b64_list_response_start + kSignedListLen,
raw_certificate_provisioning_service_response.begin() +
b64_list_response_end);
// Strip off quotes.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\"'),
signed_list.end());
// Strip off spaces.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), ' '),
signed_list.end());
// Strip off newlines.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\n'),
signed_list.end());
// Strip off carriage return (the control-M character)
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\r'),
signed_list.end());
if (!absl::WebSafeBase64Unescape(
signed_list, &serialized_signed_certificate_status_list)) {
if (!absl::Base64Unescape(signed_list,
&serialized_signed_certificate_status_list)) {
return Status(error_space, error::INVALID_ARGUMENT,
"Base64 decode of signedlist failed.");
}
}
} else {
// If this was not a legacy JSON response, attempt to deserialize the base64
// response.
if (!absl::WebSafeBase64Unescape(
raw_certificate_provisioning_service_response,
&serialized_signed_certificate_status_list)) {
if (!absl::Base64Unescape(raw_certificate_provisioning_service_response,
&serialized_signed_certificate_status_list)) {
return Status(error_space, error::INVALID_ARGUMENT,
"Base64 decode of certList failed.");
}
}
}
// Attempt to parse the legacy serialized signed status list into the proto
// and extract the serialized status list and signature.
return ParseLegacySignedDeviceCertificateStatusList(
serialized_signed_certificate_status_list,
serialized_certificate_status_list, hash_algorithm, signature);
}
Status DeviceStatusList::ExtractPublishedDevicesInfo(
const std::string& serialized_published_devices,
std::string* serialized_certificate_status_list,
HashAlgorithm* hash_algorithm, std::string* signature) {
// TODO(b/139067045): Change from using the SignedDeviceInfo proto
// to using the correct proto from the API. This duplicate, wire-compatible
// proto was a temporary way to workaround Proto2/Proto3 compatibility issues.
SignedDeviceInfo devices_info;
if (!devices_info.ParseFromString(serialized_published_devices)) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"published-devices-info-parse-error");
}
*serialized_certificate_status_list =
devices_info.device_certificate_status_list();
*hash_algorithm = HashAlgorithmProtoToEnum(devices_info.hash_algorithm());
*signature = devices_info.signature();
return OkStatus();
}
Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest(
const std::string& version,
const std::string& serialized_service_certificate,
std::string* signed_device_certificate_status_list_request) {
if (version.empty()) {
return Status(error_space, error::INVALID_ARGUMENT, "SDK version is empty");
}
DCHECK(signed_device_certificate_status_list_request);
if (signed_device_certificate_status_list_request == nullptr) {
return Status(error_space, error::INVALID_ARGUMENT,
"Signed_device_certificate_status_list_request is empty");
}
// Construct SignedDeviceCertificateStatusListRequest.
DeviceCertificateStatusListRequest request;
request.set_sdk_version(version);
request.set_sdk_time_seconds(DeviceStatusList::Instance()->GetCurrentTime());
request.set_service_certificate(serialized_service_certificate);
std::string device_certificate_status_list_request;
request.SerializeToString(&device_certificate_status_list_request);
SignedDeviceInfoRequest signed_request;
signed_request.set_device_certificate_status_list_request(
device_certificate_status_list_request);
const DrmServiceCertificate* sc =
DrmServiceCertificate::GetDefaultDrmServiceCertificate();
if (sc == nullptr) {
signed_device_certificate_status_list_request->clear();
return Status(error_space, widevine::SERVICE_CERTIFICATE_NOT_FOUND,
"Drm service certificate is not loaded.");
}
const RsaPrivateKey* private_key = sc->private_key();
if (private_key == nullptr) {
return Status(error_space, widevine::INVALID_SERVICE_PRIVATE_KEY,
"Private key in the service certificate is null.");
}
std::string signature;
private_key->GenerateSignature(
device_certificate_status_list_request,
HashAlgorithmProtoToEnum(signed_request.hash_algorithm()), &signature);
signed_request.set_signature(signature);
signed_request.SerializeToString(
signed_device_certificate_status_list_request);
return OkStatus();
}
Status DeviceStatusList::ParseLegacySignedDeviceCertificateStatusList(
const std::string& serialized_signed_device_certificate_status_list,
std::string* serialized_device_certificate_status_list,
HashAlgorithm* hash_algorithm, std::string* signature) {
// Parse the serialized_signed_device_certificate_status_list to extract the
// serialized_device_certificate_status_list
SignedDeviceCertificateStatusList signed_device_list;
if (!signed_device_list.ParseFromString(
serialized_signed_device_certificate_status_list)) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"signed-certificate-status-list-parse-error");
}
if (signed_device_list.certificate_status_list().empty()) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"missing-status-list");
}
if (signed_device_list.signature().empty()) {
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"missing-status-list-signature");
}
*serialized_device_certificate_status_list =
signed_device_list.certificate_status_list();
*hash_algorithm =
HashAlgorithmProtoToEnum(signed_device_list.hash_algorithm());
*signature = signed_device_list.signature();
return OkStatus();
}
void DeviceStatusList::RevokedDrmCertificateSerialNumbers(
const std::string& drm_certificate_serial_numbers) {
for (absl::string_view drm_certificate_serial_number :
absl::StrSplit(drm_certificate_serial_numbers, ',')) {
revoked_drm_certificate_serial_numbers_.insert(
std::string(drm_certificate_serial_number));
}
}
bool DeviceStatusList::IsDrmCertificateRevoked(
const std::string& device_certificate_serial_number) {
if (revoked_drm_certificate_serial_numbers_.find(
device_certificate_serial_number) !=
revoked_drm_certificate_serial_numbers_.end()) {
return true;
}
return false;
}
Status DeviceStatusList::GetDeviceCertificateStatusBySystemId(
uint32_t system_id, DeviceCertificateStatus* device_certificate_status) {
absl::ReaderMutexLock lock(&status_map_lock_);
if (expiration_period_seconds_ &&
(GetCurrentTime() >
(creation_time_seconds_ + expiration_period_seconds_))) {
return Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST,
"certificate-status-list-expired");
}
DeviceCertificateStatus* device_cert_status =
gtl::FindOrNull(device_status_map_, system_id);
if (device_cert_status == nullptr) {
return Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN,
"device-certificate-status-unknown");
} else {
*device_certificate_status = *device_cert_status;
}
return OkStatus();
}
} // namespace widevine