//////////////////////////////////////////////////////////////////////////////// // 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 #include #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 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 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 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::iterator, std::multimap::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::iterator, std::multimap::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":"" // 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