363 lines
15 KiB
C++
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
|