//////////////////////////////////////////////////////////////////////////////// // 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 #include #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 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