Refactor and cleanup codes. No functional changes.
This commit is contained in:
362
common/device_status_list.cc
Normal file
362
common/device_status_list.cc
Normal file
@@ -0,0 +1,362 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 <memory>
|
||||
|
||||
#include "glog/logging.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/rsa_key.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
|
||||
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()
|
||||
: creation_time_seconds_(0),
|
||||
expiration_period_seconds_(0),
|
||||
allow_unknown_devices_(true),
|
||||
allow_test_only_devices_(false) {}
|
||||
|
||||
DeviceStatusList::~DeviceStatusList() {}
|
||||
|
||||
Status DeviceStatusList::UpdateStatusList(
|
||||
const std::string& root_certificate_public_key,
|
||||
const std::string& serialized_certificate_status_list,
|
||||
uint32_t expiration_period_seconds) {
|
||||
SignedDeviceCertificateStatusList signed_certificate_status_list;
|
||||
if (!signed_certificate_status_list.ParseFromString(
|
||||
serialized_certificate_status_list)) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"signed-certificate-status-list-parse-error");
|
||||
}
|
||||
if (!signed_certificate_status_list.has_certificate_status_list()) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"missing-status-list");
|
||||
}
|
||||
if (!signed_certificate_status_list.has_signature()) {
|
||||
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(
|
||||
signed_certificate_status_list.certificate_status_list(),
|
||||
signed_certificate_status_list.signature())) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"invalid-status-list-signature");
|
||||
}
|
||||
DeviceCertificateStatusList certificate_status_list;
|
||||
if (!certificate_status_list.ParseFromString(
|
||||
signed_certificate_status_list.certificate_status_list())) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"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,
|
||||
ProvisionedDeviceInfo* device_info) {
|
||||
CHECK(device_info);
|
||||
|
||||
// Keybox checks.
|
||||
if (client_cert.type() == ClientIdentification::KEYBOX) {
|
||||
if (!KeyboxClientCert::IsSystemIdKnown(client_cert.system_id())) {
|
||||
return Status(error_space, UNSUPPORTED_SYSTEM_ID,
|
||||
"keybox-unsupported-system-id");
|
||||
}
|
||||
// Get device information from certificate status list if available.
|
||||
if (!GetDeviceInfo(client_cert, device_info)) {
|
||||
device_info->Clear();
|
||||
}
|
||||
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");
|
||||
}
|
||||
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) {
|
||||
*device_info = device_cert_status->device_info();
|
||||
if (device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_REVOKED) {
|
||||
if (IsRevokedSystemIdAllowed(client_cert.system_id())) {
|
||||
LOG(WARNING) << "Allowing REVOKED device: "
|
||||
<< device_info->ShortDebugString();
|
||||
} else {
|
||||
return Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED,
|
||||
"device-certificate-revoked");
|
||||
}
|
||||
}
|
||||
if ((device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_TEST_ONLY) &&
|
||||
!allow_test_only_devices_) {
|
||||
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 Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
"device-certificate-status-unknown");
|
||||
}
|
||||
} else {
|
||||
if (!allow_unknown_devices_) {
|
||||
return Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
"device-certificate-status-unknown");
|
||||
}
|
||||
device_info->Clear();
|
||||
}
|
||||
|
||||
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) {
|
||||
*device_info = device_cert_status->device_info();
|
||||
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());
|
||||
}
|
||||
|
||||
bool DeviceStatusList::IsRevokedSystemIdAllowed(uint32_t system_id) {
|
||||
auto it = std::binary_search(allowed_revoked_devices_.begin(),
|
||||
allowed_revoked_devices_.end(), system_id);
|
||||
return it;
|
||||
}
|
||||
|
||||
Status DeviceStatusList::ExtractFromProvisioningServiceResponse(
|
||||
const std::string& certificate_provisioning_service_response,
|
||||
std::string* signed_certificate_status_list, std::string* certificate_status_list) {
|
||||
Status status = OkStatus();
|
||||
size_t signed_list_start =
|
||||
certificate_provisioning_service_response.find(kSignedList);
|
||||
if (signed_list_start != std::string::npos) {
|
||||
size_t signed_list_end = certificate_provisioning_service_response.find(
|
||||
kSignedListTerminator, signed_list_start);
|
||||
if (signed_list_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(
|
||||
certificate_provisioning_service_response.begin() + signed_list_start +
|
||||
kSignedListLen,
|
||||
certificate_provisioning_service_response.begin() + signed_list_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,
|
||||
signed_certificate_status_list)) {
|
||||
if (!absl::Base64Unescape(signed_list, signed_certificate_status_list)) {
|
||||
return Status(error_space, error::INVALID_ARGUMENT,
|
||||
"Base64 decode of signedlist failed.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// certificate_provisioning_service_response is the signed list and not a
|
||||
// JSON message.
|
||||
if (!absl::WebSafeBase64Unescape(certificate_provisioning_service_response,
|
||||
signed_certificate_status_list)) {
|
||||
if (!absl::Base64Unescape(certificate_provisioning_service_response,
|
||||
signed_certificate_status_list)) {
|
||||
return Status(error_space, error::INVALID_ARGUMENT,
|
||||
"Base64 decode of certList failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
SignedDeviceCertificateStatusList signed_status_list;
|
||||
if (!signed_status_list.ParseFromString(*signed_certificate_status_list)) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"signed-certificate-status-list-parse-error");
|
||||
}
|
||||
if (!signed_status_list.has_certificate_status_list()) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"missing-status-list");
|
||||
}
|
||||
DeviceCertificateStatusList device_certificate_status_list;
|
||||
if (!device_certificate_status_list.ParseFromString(
|
||||
signed_status_list.certificate_status_list())) {
|
||||
return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"certificate-status-list-parse-error");
|
||||
}
|
||||
*certificate_status_list = signed_status_list.certificate_status_list();
|
||||
return OkStatus();
|
||||
}
|
||||
|
||||
Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest(
|
||||
const std::string& version,
|
||||
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());
|
||||
std::string device_certificate_status_list_request;
|
||||
request.SerializeToString(&device_certificate_status_list_request);
|
||||
SignedDeviceCertificateStatusListRequest 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::INVALID_SERVICE_CERTIFICATE,
|
||||
"Drm service certificate is not loaded.");
|
||||
}
|
||||
const RsaPrivateKey* private_key = sc->private_key();
|
||||
if (private_key == nullptr) {
|
||||
return Status(error_space, widevine::INVALID_SERVICE_CERTIFICATE,
|
||||
"Private key in the service certificate is null.");
|
||||
}
|
||||
std::string signature;
|
||||
private_key->GenerateSignature(device_certificate_status_list_request,
|
||||
&signature);
|
||||
signed_request.set_signature(signature);
|
||||
signed_request.SerializeToString(
|
||||
signed_device_certificate_status_list_request);
|
||||
return OkStatus();
|
||||
}
|
||||
} // namespace widevine
|
||||
Reference in New Issue
Block a user