Files
provisioning_sdk_source/common/device_status_list.cc
2019-01-23 15:16:31 -08:00

363 lines
15 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 <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