// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "device_files.h" #include #include #include #include #include "cdm_random.h" #include "certificate_provisioning.h" #include "clock.h" #include "file_store.h" #include "license_protocol.pb.h" #include "log.h" #include "privacy_crypto.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::HashedFile; using video_widevine_client::sdk::HlsAttributes; using video_widevine_client::sdk::HlsAttributes_Method_AES_128; using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES; using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; using video_widevine_client::sdk::NameValue; using video_widevine_client::sdk::OemCertificate; // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" #define STRINGIFY(PARAM) #PARAM #define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return DeviceFiles::kCannotHandle; \ } #define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED() \ if (!initialized_) { \ LOGE("Device files is not initialized"); \ return DeviceFiles::kCannotHandle; \ } #define RETURN_FALSE_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return false; \ } #define RETURN_FALSE_WITH_RESULT_IF_NULL(PARAM, result) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ *result = kParameterNull; \ return false; \ } #define RETURN_FALSE_IF_UNINITIALIZED() \ if (!initialized_) { \ LOGE("Device files is not initialized"); \ return false; \ } #define RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result) \ if (!initialized_) { \ LOGE("Device files is not initialized"); \ *result = kObjectNotInitialized; \ return false; \ } namespace wvcdm { using UniqueLock = std::unique_lock; namespace { const char kEmptyFileName[] = ""; const char kFalse[] = "false"; const char kHlsAttributesFileNameExt[] = ".hal"; const char kLicenseFileNameExt[] = ".lic"; const char kTrue[] = "true"; const char kUsageInfoFileNameExt[] = ".bin"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageTableFileName[] = "usgtable.bin"; const char kOkpInfoFileName[] = "okp.bin"; const char kWildcard[] = "*"; // TODO(b/192430982): Renable expiration of legacy DRM certificates // constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60; // Helper methods bool SetDeviceCertificate(const std::string& certificate, const CryptoWrappedKey& private_key, DeviceCertificate* mutable_device_certificate) { RETURN_FALSE_IF_NULL(mutable_device_certificate); mutable_device_certificate->set_certificate(certificate); mutable_device_certificate->set_wrapped_private_key(private_key.key()); switch (private_key.type()) { case CryptoWrappedKey::kRsa: mutable_device_certificate->set_key_type(DeviceCertificate::RSA); return true; case CryptoWrappedKey::kEcc: mutable_device_certificate->set_key_type(DeviceCertificate::ECC); return true; case CryptoWrappedKey::kUninitialized: // Suppress compiler // warnings. default: LOGE("Unexpected key type: %d", private_key.type()); return false; } } bool ExtractFromDeviceCertificate(const DeviceCertificate& device_certificate, std::string* certificate, CryptoWrappedKey* private_key) { RETURN_FALSE_IF_NULL(certificate); RETURN_FALSE_IF_NULL(private_key); bool has_certificate = device_certificate.has_certificate(); bool has_key = device_certificate.has_wrapped_private_key(); // If no certificate information, nothing to be done. DeviceCertificate // is a legacy DRM certificate if (!has_certificate && !has_key) return true; // Flag if not a default certificate if (!has_certificate || !has_key) { LOGE( "Device certificate proto belongs to neither a default or legacy cert. " "has_certificate: %s, has_key: %s", has_certificate ? kTrue : kFalse, has_key ? kTrue : kFalse); return false; } if (device_certificate.certificate().empty() || device_certificate.wrapped_private_key().empty()) { LOGE( "Device certificate proto belongs does not have a valid certificate or " "wrapped key. certificate size: %zu, wrapped key size: %zu", device_certificate.certificate().size(), device_certificate.wrapped_private_key().size()); return false; } *certificate = device_certificate.certificate(); private_key->Clear(); private_key->set_key(device_certificate.wrapped_private_key()); if (device_certificate.has_key_type()) { const DeviceCertificate::PrivateKeyType key_type = device_certificate.key_type(); switch (key_type) { case DeviceCertificate::RSA: private_key->set_type(CryptoWrappedKey::kRsa); break; case DeviceCertificate::ECC: private_key->set_type(CryptoWrappedKey::kEcc); break; default: LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type); private_key->set_type(CryptoWrappedKey::kRsa); break; } } else { // Possible that device certificate is from V15, in this case, the // only supported key of at that time was RSA. LOGD("No key type info, assuming RSA"); private_key->set_type(CryptoWrappedKey::kRsa); } return true; } } // namespace // static const char* DeviceFiles::CertificateStateToString(CertificateState state) { switch (state) { case kCertificateValid: return "Valid"; case kCertificateExpired: return "Expired"; case kCertificateNotFound: return "NotFound"; case kCertificateInvalid: return "Invalid"; case kCannotHandle: return "CannotHandle"; } return UnknownEnumValueToString(static_cast(state)); } // static const char* DeviceFiles::CertificateTypeToString(CertificateType type) { switch (type) { case kCertificateDefault: return "Default"; case kCertificateLegacy: return "Legacy"; case kCertificateAtsc: return "ATSC"; } return UnknownEnumValueToString(static_cast(type)); } // static const char* DeviceFiles::ResponseTypeToString(ResponseType type) { switch (type) { case kNoError: return "NoError"; case kObjectNotInitialized: return "ObjectNotInitialized"; case kParameterNull: return "ParameterNull"; case kBasePathUnavailable: return "PathUnavailable"; case kFileNotFound: return "NotFound"; case kFileOpenFailed: return "OpenFailed"; case kFileWriteError: return "WriteError"; case kFileReadError: return "ReadError"; case kInvalidFileSize: return "InvalidFileSize"; case kHashComputationFailed: return "HashFailed"; case kFileHashMismatch: return "HashMismatch"; case kFileParseError1: return "ParseHashedFileError"; case kFileParseError2: return "ParseFileError"; case kUnknownLicenseState: return "UnknownLicenseState"; case kIncorrectFileType: return "IncorrectFileType"; case kIncorrectFileVersion: return "IncorrectFileVersion"; case kLicenseNotPresent: return "LicenseNotFound"; case kResponseTypeBase: // Not a valid value. break; } return UnknownEnumValueToString(static_cast(type)); } // static std::set DeviceFiles::reserved_license_ids_; std::mutex DeviceFiles::reserved_license_ids_mutex_; DeviceFiles::DeviceFiles(wvutil::FileSystem* file_system) : file_system_(file_system), security_level_(kSecurityLevelUninitialized), initialized_(false) {} DeviceFiles::~DeviceFiles() {} bool DeviceFiles::Init(CdmSecurityLevel security_level) { if (!file_system_) { LOGE("Invalid wvutil::FileSystem given"); return false; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level, &path)) { LOGE("Unsupported security level: %d", security_level); return false; } security_level_ = security_level; initialized_ = true; return true; } bool DeviceFiles::StoreCertificate(const std::string& certificate, const CryptoWrappedKey& private_key) { RETURN_FALSE_IF_UNINITIALIZED(); if (certificate.empty()) { LOGE("Missing certificate information"); return false; } if (!private_key.IsValid()) { LOGE("Private key is invalid"); return false; } // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE); file.set_version(video_widevine_client::sdk::File::VERSION_1); DeviceCertificate* device_certificate = file.mutable_device_certificate(); int64_t creation_time_seconds; int64_t expiration_time_seconds; uint32_t system_id; if (!CertificateProvisioning::ExtractDeviceInfo( certificate, nullptr, &system_id, &creation_time_seconds, &expiration_time_seconds)) return false; if (creation_time_seconds <= 0) { LOGE("Invalid certificate creation time %" PRId64, creation_time_seconds); return false; } const bool default_certificate = expiration_time_seconds >= 0; if (!SetDeviceCertificate(certificate, private_key, device_certificate)) return false; if (default_certificate) { wvutil::Clock clock; device_certificate->set_acquisition_time_seconds(clock.GetCurrentTime()); } /* TODO(b/192430982): Renable expiration of legacy DRM certificates else { // Since certificates of type kCertificateAtsc are not allowed to be // stored, this is a certificate of type kCertificateLegacy. // The only time when a legacy certificate is stored is when it does not // have an expiration time. Set expiration time to 6 months +- 2 months. wvutil::Clock clock; const int64_t current_time = clock.GetCurrentTime(); wvutil::CdmRandomGenerator rng(current_time & 0xffffffff); device_certificate->set_expiration_time_seconds( current_time + kFourMonthsInSeconds + rng.RandomInRange(kFourMonthsInSeconds)); } */ std::string serialized_file; file.SerializeToString(&serialized_file); std::string certificate_file_name; const CertificateType certificate_type = default_certificate ? kCertificateDefault : kCertificateLegacy; if (!GetCertificateFileName(certificate_type, &certificate_file_name)) { LOGE("Unable to get certificate file name of type: %d", certificate_type); return false; } return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError; } DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate( bool atsc_mode_enabled, std::string* certificate, CryptoWrappedKey* private_key, std::string* serial_number, uint32_t* system_id) { RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED(); RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(private_key); if (!HasCertificate(atsc_mode_enabled)) { LOGW("Unable to find certificate, atsc mode: %s", atsc_mode_enabled ? "enabled" : "disabled"); return kCertificateNotFound; } if (atsc_mode_enabled) return RetrieveCertificate(kCertificateAtsc, certificate, private_key, serial_number, system_id); if (HasCertificate(kCertificateDefault)) return RetrieveCertificate(kCertificateDefault, certificate, private_key, serial_number, system_id); return RetrieveCertificate(kCertificateLegacy, certificate, private_key, serial_number, system_id); } DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate( CertificateType certificate_type, std::string* certificate, CryptoWrappedKey* wrapped_private_key, std::string* serial_number, uint32_t* system_id) { RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key); std::string certificate_file_name; if (!GetCertificateFileName(certificate_type, &certificate_file_name)) { LOGW("Unable to find certificate file name for type: %d", certificate_type); return kCannotHandle; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) { LOGW("Unable to retrieve certificate file"); return kCertificateNotFound; } if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { LOGE("Certificate file is of incorrect file type: type = %d", static_cast(file.type())); return kCertificateInvalid; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Certificate file is of incorrect file version: version = %d", static_cast(file.version())); return kCertificateInvalid; } if (!file.has_device_certificate()) { LOGE("Certificate not present"); return kCertificateInvalid; } const DeviceCertificate& device_certificate = file.device_certificate(); if (!ExtractFromDeviceCertificate(device_certificate, certificate, wrapped_private_key)) { LOGE("Unable to extract from device certificate"); return kCertificateInvalid; } int64_t creation_time_seconds; int64_t expiration_time_seconds; if (!CertificateProvisioning::ExtractDeviceInfo( device_certificate.certificate(), serial_number, system_id, &creation_time_seconds, &expiration_time_seconds)) return kCertificateInvalid; wvutil::Clock clock; const int64_t current_time = clock.GetCurrentTime(); switch (certificate_type) { case kCertificateDefault: { // Validation check for DRM certificate that includes an expiration // time set by the provisioning service. Since provisioning and // client clocks may not be in sync, verify by comparing time // elapsed since the certificate was acquired with the expiration // period specified by the service. // First verify that all the fields are set to valid values. // The service will validate certificate expiration so tampering of // time values at the client is not a concern. if (creation_time_seconds <= 0) { LOGE("Invalid creation time of default certificate: %" PRId64, creation_time_seconds); return kCertificateInvalid; } if (expiration_time_seconds < 0) { LOGE("Invalid expiration time of default certificate: %" PRId64, expiration_time_seconds); return kCertificateInvalid; } if (expiration_time_seconds == UNLIMITED_DURATION) return kCertificateValid; if (!device_certificate.has_acquisition_time_seconds()) { LOGE("Acquisition time of default certificate not available"); return kCertificateInvalid; } const int64_t acquisition_time_seconds = device_certificate.acquisition_time_seconds(); if (acquisition_time_seconds <= 0) { LOGE("Invalid acquisition time of default certificate: %" PRId64, acquisition_time_seconds); return kCertificateInvalid; } if (current_time < acquisition_time_seconds) { LOGE("Time not valid: current time: %" PRId64 ", acquisition time: %" PRId64, current_time, acquisition_time_seconds); return kCannotHandle; } if (expiration_time_seconds < creation_time_seconds) { LOGE("Time not valid: expiration time: %" PRId64 ", creation time: %" PRId64, expiration_time_seconds, creation_time_seconds); return kCertificateInvalid; } // |current_time| and |acquisition_time_seconds| are client clock // times while |expiration_time_seconds| and |creation_time_seconds| // are times specified by the provisioning service if (current_time - acquisition_time_seconds > expiration_time_seconds - creation_time_seconds) { return kCertificateExpired; } return kCertificateValid; } case kCertificateLegacy: { /* TODO(b/192430982): Renable expiration of legacy DRM certificates // Validation check for DRM certificate without an expiration // time set by the provisioning service. Add an expiry time // within the next 6 months +/- 2 months, if one has not been set. if (!device_certificate.has_expiration_time_seconds()) { StoreCertificate(*certificate, *wrapped_private_key); return kCertificateValid; } const int64_t expiration_time_seconds = device_certificate.expiration_time_seconds(); if (expiration_time_seconds <= 0) { LOGE("Invalid expiration time of legacy certificate: %" PRId64, expiration_time_seconds); return kCertificateInvalid; } if (current_time > expiration_time_seconds) return kCertificateExpired; */ return kCertificateValid; } case kCertificateAtsc: // No expiration enforced return kCertificateValid; default: // Should never happen. This should be detected earlier when fetching // the file name LOGE("Invalid certificate type: %d", certificate_type); return kCertificateInvalid; } } bool DeviceFiles::RetrieveLegacyCertificate(std::string* certificate, CryptoWrappedKey* private_key, std::string* serial_number, uint32_t* system_id) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(certificate); RETURN_FALSE_IF_NULL(private_key); if (!HasCertificate(kCertificateLegacy)) return false; const CertificateState state = RetrieveCertificate( kCertificateLegacy, certificate, private_key, serial_number, system_id); if (state == kCertificateValid || state == kCertificateExpired) return true; return false; } bool DeviceFiles::HasCertificate(bool atsc_mode_enabled) { RETURN_FALSE_IF_UNINITIALIZED(); if (atsc_mode_enabled) return HasCertificate(kCertificateAtsc); return HasCertificate(kCertificateDefault) || HasCertificate(kCertificateLegacy); } bool DeviceFiles::RemoveCertificate() { RETURN_FALSE_IF_UNINITIALIZED() std::string certificate_file_name; if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name)) RemoveFile(certificate_file_name); if (GetCertificateFileName(kCertificateDefault, &certificate_file_name)) return RemoveFile(certificate_file_name); return true; } bool DeviceFiles::RemoveOemCertificate() { RETURN_FALSE_IF_UNINITIALIZED() std::string certificate_file_name; if (GetOemCertificateFileName(&certificate_file_name)) { return RemoveFile(certificate_file_name); } return true; } bool DeviceFiles::StoreOemCertificate(const std::string& certificate, const CryptoWrappedKey& private_key) { RETURN_FALSE_IF_UNINITIALIZED(); if (certificate.empty()) { LOGE("Missing certificate information"); return false; } if (!private_key.IsValid()) { LOGE("Private key is invalid"); return false; } std::string certificate_file_name; if (!GetOemCertificateFileName(&certificate_file_name)) { LOGE("Unable to get certificate file name"); return false; } // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE); file.set_version(video_widevine_client::sdk::File::VERSION_1); OemCertificate* oem_certificate = file.mutable_oem_certificate(); oem_certificate->set_certificate(certificate); oem_certificate->set_wrapped_private_key(private_key.key()); switch (private_key.type()) { case wvcdm::CryptoWrappedKey::kRsa: oem_certificate->set_key_type(OemCertificate::RSA); break; case wvcdm::CryptoWrappedKey::kEcc: oem_certificate->set_key_type(OemCertificate::ECC); break; case wvcdm::CryptoWrappedKey::kUninitialized: default: LOGE("Unexpected key type: %d", private_key.type()); return false; } std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError; } DeviceFiles::CertificateState DeviceFiles::RetrieveOemCertificate( std::string* certificate, CryptoWrappedKey* wrapped_private_key) { RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED(); RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key); std::string certificate_file_name; if (!GetOemCertificateFileName(&certificate_file_name)) { LOGW("Unable to find certificate file name"); return kCannotHandle; } video_widevine_client::sdk::File file; if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) { LOGW("Unable to retrieve certificate file"); return kCertificateNotFound; } if (file.type() != video_widevine_client::sdk::File::OEM_CERTIFICATE) { LOGE("Certificate file is of incorrect file type: type = %d", static_cast(file.type())); return kCertificateInvalid; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Certificate file is of incorrect file version: version = %d", static_cast(file.version())); return kCertificateInvalid; } if (!file.has_oem_certificate()) { LOGE("Certificate not present"); return kCertificateInvalid; } const OemCertificate& oem_certificate = file.oem_certificate(); if (oem_certificate.certificate().empty() || oem_certificate.wrapped_private_key().empty()) { LOGE("Empty certificate or private key"); return kCertificateInvalid; } *certificate = oem_certificate.certificate(); wrapped_private_key->Clear(); wrapped_private_key->set_key(oem_certificate.wrapped_private_key()); switch (oem_certificate.key_type()) { case OemCertificate::RSA: wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa); break; case OemCertificate::ECC: wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kEcc); break; default: LOGW("Unknown key type, defaulting to RSA: type = %d", oem_certificate.key_type()); wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa); break; } return kCertificateValid; } bool DeviceFiles::HasOemCertificate() { RETURN_FALSE_IF_UNINITIALIZED(); std::string certificate_file_name; if (!GetOemCertificateFileName(&certificate_file_name)) { return false; } return FileExists(certificate_file_name); } bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, ResponseType* result) { RETURN_FALSE_IF_NULL(result); *result = kNoError; RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::LICENSE); file.set_version(video_widevine_client::sdk::File::VERSION_1); License* license = file.mutable_license(); switch (license_data.state) { case kLicenseStateActive: license->set_state(License_LicenseState_ACTIVE); break; case kLicenseStateReleasing: license->set_state(License_LicenseState_RELEASING); break; default: LOGE("Unknown license state: %d", static_cast(license_data.state)); *result = kUnknownLicenseState; return false; break; } license->set_pssh_data(license_data.pssh_data); license->set_license_request(license_data.license_request); license->set_license(license_data.license); license->set_renewal_request(license_data.license_renewal_request); license->set_renewal(license_data.license_renewal); license->set_release_server_url(license_data.release_server_url); license->set_playback_start_time(license_data.playback_start_time); license->set_last_playback_time(license_data.last_playback_time); license->set_grace_period_end_time(license_data.grace_period_end_time); for (CdmAppParameterMap::const_iterator iter = license_data.app_parameters.cbegin(); iter != license_data.app_parameters.cend(); ++iter) { NameValue* app_params = license->add_app_parameters(); app_params->set_name(iter->first); app_params->set_value(iter->second); } license->set_usage_entry(license_data.usage_entry); license->set_usage_entry_number(license_data.usage_entry_number); if (!license_data.drm_certificate.empty()) { DeviceCertificate* device_certificate = license->mutable_drm_certificate(); if (!SetDeviceCertificate(license_data.drm_certificate, license_data.wrapped_private_key, device_certificate)) return false; } std::string serialized_file; file.SerializeToString(&serialized_file); UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(license_data.key_set_id); *result = StoreFileWithHash(license_data.key_set_id + kLicenseFileNameExt, serialized_file); return *result == kNoError; } bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, CdmLicenseData* license_data, ResponseType* result) { // This check must be made first as the RETURN_FALSE_IF_NULL() macro // will assign kParameterNull to |result|. if (result == nullptr) { LOGE("Output parameter |result| not provided"); return false; } RETURN_FALSE_WITH_RESULT_IF_UNINITIALIZED(result); RETURN_FALSE_WITH_RESULT_IF_NULL(license_data, result); video_widevine_client::sdk::File file; *result = RetrieveHashedFile(key_set_id + kLicenseFileNameExt, &file); if (*result != kNoError) { LOGE("Unable to retrieve key set license file: result = %s", ResponseTypeToString(*result)); return false; } if (file.type() != video_widevine_client::sdk::File::LICENSE) { LOGE("Incorrect file type: type = %d, expected_type = %d", static_cast(file.type()), static_cast(video_widevine_client::sdk::File::LICENSE)); *result = kIncorrectFileType; return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d, expected_version = %d", static_cast(file.version()), static_cast(video_widevine_client::sdk::File::VERSION_1)); *result = kIncorrectFileVersion; return false; } if (!file.has_license()) { LOGE("License not present"); *result = kLicenseNotPresent; return false; } license_data->key_set_id = key_set_id; License license = file.license(); switch (license.state()) { case License_LicenseState_ACTIVE: license_data->state = kLicenseStateActive; break; case License_LicenseState_RELEASING: license_data->state = kLicenseStateReleasing; break; default: LOGW("Unrecognized license state: %d", static_cast(license.state())); license_data->state = kLicenseStateUnknown; break; } license_data->pssh_data = license.pssh_data(); license_data->license_request = license.license_request(); license_data->license = license.license(); license_data->license_renewal_request = license.renewal_request(); license_data->license_renewal = license.renewal(); license_data->release_server_url = license.release_server_url(); license_data->playback_start_time = license.playback_start_time(); license_data->last_playback_time = license.last_playback_time(); license_data->grace_period_end_time = license.grace_period_end_time(); for (int i = 0; i < license.app_parameters_size(); ++i) { license_data->app_parameters[license.app_parameters(i).name()] = license.app_parameters(i).value(); } license_data->usage_entry = license.usage_entry(); license_data->usage_entry_number = static_cast(license.usage_entry_number()); if (!license.has_drm_certificate()) { license_data->drm_certificate.clear(); license_data->wrapped_private_key.Clear(); return true; } return ExtractFromDeviceCertificate(license.drm_certificate(), &license_data->drm_certificate, &license_data->wrapped_private_key); } bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ListLicenses(std::vector* key_set_ids) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(key_set_ids); // Get list of filenames std::vector filenames; if (!ListFiles(&filenames)) { return false; } // Scan list of returned filenames, remove extension, and return // as a list of key_set_ids. key_set_ids->clear(); for (size_t i = 0; i < filenames.size(); i++) { std::string* name = &filenames[i]; std::size_t pos = name->find(kLicenseFileNameExt); if (pos == std::string::npos) { // Skip this file - extension does not match continue; } // Store filename (minus extension). This should be a key set ID. key_set_ids->push_back(name->substr(0, pos)); } return true; } bool DeviceFiles::DeleteAllLicenses() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(std::string(kWildcard) + kLicenseFileNameExt); } bool DeviceFiles::DeleteAllFiles() { RETURN_FALSE_IF_UNINITIALIZED(); // We pass an empty string to RemoveFile to delete the device files base // directory itself. return RemoveFile(kEmptyFileName); } bool DeviceFiles::LicenseExists(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); UniqueLock lock(reserved_license_ids_mutex_); return reserved_license_ids_.count(key_set_id) || FileExists(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.insert(key_set_id); return true; } bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(key_set_id); return true; } bool DeviceFiles::DeleteAllUsageInfo() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(kUsageInfoFileNamePrefix + std::string(kWildcard) + kUsageInfoFileNameExt); } bool DeviceFiles::ListUsageInfoFiles( std::vector* usage_info_file_names) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_info_file_names); // Get list of filenames std::vector filenames; if (!ListFiles(&filenames)) { return false; } // Scan list of all filenames and return only usage info filenames usage_info_file_names->clear(); for (size_t i = 0; i < filenames.size(); i++) { std::string* name = &filenames[i]; std::size_t pos_prefix = name->find(kUsageInfoFileNamePrefix); std::size_t pos_suffix = name->find(kUsageInfoFileNameExt); if (pos_prefix == std::string::npos || pos_suffix == std::string::npos) { // Skip this file - extension does not match continue; } usage_info_file_names->push_back(*name); } return true; } bool DeviceFiles::StoreHlsAttributes( const std::string& key_set_id, const CdmHlsMethod method, const std::vector& media_segment_iv) { RETURN_FALSE_IF_UNINITIALIZED(); // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES); file.set_version(video_widevine_client::sdk::File::VERSION_1); HlsAttributes* hls_attributes = file.mutable_hls_attributes(); switch (method) { case kHlsMethodAes128: hls_attributes->set_method(HlsAttributes_Method_AES_128); break; case kHlsMethodSampleAes: hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES); break; case kHlsMethodNone: default: LOGE("Unknown HLS method: %d", method); return false; break; } hls_attributes->set_media_segment_iv(&media_segment_iv[0], media_segment_iv.size()); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt, serialized_file) == kNoError; } bool DeviceFiles::RetrieveHlsAttributes( const std::string& key_set_id, CdmHlsMethod* method, std::vector* media_segment_iv) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file) != kNoError) { LOGE("Unable to retrieve key set HLS attributes file"); return false; } if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) { LOGE("Incorrect file type: type = %d", static_cast(file.type())); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d", static_cast(file.version())); return false; } if (!file.has_hls_attributes()) { LOGE("HLS attributes not present"); return false; } HlsAttributes attributes = file.hls_attributes(); switch (attributes.method()) { case HlsAttributes_Method_AES_128: *method = kHlsMethodAes128; break; case HlsAttributes_Method_SAMPLE_AES: *method = kHlsMethodSampleAes; break; default: LOGW("Unrecognized HLS method: %d", static_cast(attributes.method())); *method = kHlsMethodNone; break; } media_segment_iv->assign(attributes.media_segment_iv().begin(), attributes.media_segment_iv().end()); return true; } bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(key_set_id + kHlsAttributesFileNameExt); } bool DeviceFiles::StoreUsageTableInfo( const CdmUsageTableHeader& usage_table_header, const std::vector& usage_entry_info) { RETURN_FALSE_IF_UNINITIALIZED(); using video_widevine_client::sdk::UsageTableInfo; using UsageEntryInfo = UsageTableInfo::UsageEntryInfo; // Fill in file information video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::USAGE_TABLE_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); UsageTableInfo* stored_table_info = file.mutable_usage_table_info(); stored_table_info->set_usage_table_header(usage_table_header); for (const auto& entry_info : usage_entry_info) { UsageEntryInfo* stored_info = stored_table_info->add_usage_entry_info(); if (entry_info.storage_type == kStorageLicense) { stored_info->set_storage(UsageEntryInfo::LICENSE); stored_info->set_key_set_id(entry_info.key_set_id); stored_info->set_last_use_time(entry_info.last_use_time); stored_info->set_offline_license_expiry_time( entry_info.offline_license_expiry_time); } else { stored_info->set_storage(UsageEntryInfo::UNKNOWN); } } stored_table_info->set_use_lru(true); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(GetUsageTableFileName(), serialized_file) == kNoError; } bool DeviceFiles::RetrieveUsageTableInfo( CdmUsageTableHeader* usage_table_header, std::vector* usage_entry_info, bool* lru_upgrade, bool* has_usage_info_entries) { RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(usage_table_header); RETURN_FALSE_IF_NULL(usage_entry_info); RETURN_FALSE_IF_NULL(lru_upgrade); RETURN_FALSE_IF_NULL(has_usage_info_entries); using video_widevine_client::sdk::UsageTableInfo; using UsageEntryInfo = UsageTableInfo::UsageEntryInfo; video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) { LOGW("Unable to retrieve usage table file"); return false; } if (file.type() != video_widevine_client::sdk::File::USAGE_TABLE_INFO) { LOGE("Incorrect file type: type = %d, expected_type = %d", static_cast(file.type()), static_cast(video_widevine_client::sdk::File::USAGE_TABLE_INFO)); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d, expected_version = %d", static_cast(file.version()), static_cast(video_widevine_client::sdk::File::VERSION_1)); return false; } if (!file.has_usage_table_info()) { LOGE("Usage table info not present in file"); return false; } const UsageTableInfo& stored_table_info = file.usage_table_info(); *lru_upgrade = !stored_table_info.use_lru(); *has_usage_info_entries = false; *usage_table_header = stored_table_info.usage_table_header(); usage_entry_info->reserve(stored_table_info.usage_entry_info_size()); for (const auto& stored_entry_info : stored_table_info.usage_entry_info()) { CdmUsageEntryInfo entry_info; entry_info.Clear(); if (stored_entry_info.storage() == UsageEntryInfo::LICENSE) { entry_info.storage_type = kStorageLicense; entry_info.key_set_id = stored_entry_info.key_set_id(); entry_info.last_use_time = stored_entry_info.last_use_time(); entry_info.offline_license_expiry_time = stored_entry_info.offline_license_expiry_time(); } else if (stored_entry_info.storage() == UsageEntryInfo::USAGE_INFO) { // USAGE_INFO are deprecated, do not retrieve this entries. *has_usage_info_entries = true; } usage_entry_info->emplace_back(std::move(entry_info)); } return true; } bool DeviceFiles::DeleteUsageTableInfo() { RETURN_FALSE_IF_UNINITIALIZED(); return RemoveFile(GetUsageTableFileName()); } bool DeviceFiles::HasCertificate(CertificateType certificate_type) { RETURN_FALSE_IF_UNINITIALIZED(); std::string certificate_file_name; if (!GetCertificateFileName(certificate_type, &certificate_file_name)) return false; return FileExists(certificate_file_name); } bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) { using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; using okp::SystemState; RETURN_FALSE_IF_UNINITIALIZED(); if (security_level_ != kSecurityLevelL1) { LOGE("OKP info is only supported by L1: level = %d", static_cast(security_level_)); return false; } video_widevine_client::sdk::File file; file.set_type(video_widevine_client::sdk::File::OKP_INFO); file.set_version(video_widevine_client::sdk::File::VERSION_1); StoredOkpInfo* stored_info = file.mutable_okp_info(); switch (info.state()) { case SystemState::kNeedsProvisioning: stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING); break; case SystemState::kFallbackMode: stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE); break; case SystemState::kProvisioned: stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED); break; case SystemState::kUnknown: default: LOGE("Unexpected OKP state: state = %d", static_cast(info.state())); return false; } if (info.first_checked_time() <= 0) { LOGE("OKP first checked time is missing"); return false; } stored_info->set_first_checked_time(info.first_checked_time()); if (info.state() == SystemState::kProvisioned) { if (!info.HasProvisioningTime()) { LOGE("OKP set as provisioned, but missing provisioning time"); return false; } stored_info->set_provisioning_time(info.provisioning_time()); } else if (info.state() == SystemState::kFallbackMode) { if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) { LOGE("OKP fallback information is missing "); return false; } stored_info->set_backoff_start_time(info.backoff_start_time()); stored_info->set_backoff_duration(info.backoff_duration()); } else { if (info.HasBackoffDuration()) { // Store backoff duration from before. stored_info->set_backoff_duration(info.backoff_duration()); } } std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError; } bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) { using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; using okp::SystemState; RETURN_FALSE_IF_UNINITIALIZED(); RETURN_FALSE_IF_NULL(info); info->Clear(); if (security_level_ != kSecurityLevelL1) { LOGE("OKP info is only supported by L1: level = %d", static_cast(security_level_)); return false; } // File meta-data validation. video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) { LOGW("Unable to retrieve OKP info file"); return false; } if (file.type() != video_widevine_client::sdk::File::OKP_INFO) { LOGE("Incorrect file type: type = %d, expected_type = %d", static_cast(file.type()), static_cast(video_widevine_client::sdk::File::OKP_INFO)); return false; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Incorrect file version: version = %d, expected_version = %d", static_cast(file.version()), static_cast(video_widevine_client::sdk::File::VERSION_1)); return false; } if (!file.has_okp_info()) { // OKP info is only stored if at least 1 field is non-empty. This // must be an error. LOGD("OKP info is not present in file"); return false; } const StoredOkpInfo& stored_info = file.okp_info(); switch (stored_info.state()) { case StoredOkpInfo::OKP_NEEDS_PROVISIONING: info->SetState(SystemState::kNeedsProvisioning); break; case StoredOkpInfo::OKP_FALLBACK_MODE: info->SetState(SystemState::kFallbackMode); break; case StoredOkpInfo::OKP_PROVISIONED: info->SetState(SystemState::kProvisioned); break; case StoredOkpInfo::OKP_UNKNOWN: default: LOGE("Unexpected OKP state: stored_state = %d", static_cast(stored_info.state())); return false; } if (stored_info.first_checked_time() <= 0) { LOGE("OKP first check time not present"); info->Clear(); return false; } info->SetFirstCheckedTime(stored_info.first_checked_time()); if (info->state() == SystemState::kProvisioned) { if (stored_info.provisioning_time() <= 0) { LOGE("OKP set as provisioned, but missing provisioning time"); info->Clear(); return false; } info->SetProvisioningTime(stored_info.provisioning_time()); return true; } if (info->state() == SystemState::kFallbackMode) { if (stored_info.backoff_start_time() <= 0 || stored_info.backoff_duration() <= 0) { LOGE("OKP backoff information is missing"); info->Clear(); return false; } info->SetBackoffStartTime(stored_info.backoff_start_time()); info->SetBackoffDuration(stored_info.backoff_duration()); return true; } // Provisioned. if (stored_info.backoff_duration() > 0) { info->SetBackoffDuration(stored_info.backoff_duration()); } return true; } bool DeviceFiles::DeleteOkpInfo() { RETURN_FALSE_IF_UNINITIALIZED(); if (security_level_ != kSecurityLevelL1) { LOGE("OKP info is only supported by L1: level = %d", static_cast(security_level_)); return false; } return RemoveFile(GetOkpInfoFileName()); } DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash( const std::string& name, const std::string& serialized_file) { std::string hash = Sha256Hash(serialized_file); // Fill in hashed file data HashedFile hash_file; hash_file.set_file(serialized_file); hash_file.set_hash(hash); std::string serialized_hash_file; hash_file.SerializeToString(&serialized_hash_file); return StoreFileRaw(name, serialized_hash_file); } DeviceFiles::ResponseType DeviceFiles::StoreFileRaw( const std::string& name, const std::string& serialized_file) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return kBasePathUnavailable; } path += name; auto file = file_system_->Open( path, wvutil::FileSystem::kCreate | wvutil::FileSystem::kTruncate); if (!file) { LOGE("Failed to open file: path = %s", path.c_str()); return kFileOpenFailed; } const ssize_t bytes_written = file->Write(serialized_file.data(), serialized_file.size()); if (bytes_written < 0) { LOGE("Failed to write to file: path = %s", path.c_str()); return kFileWriteError; } if (bytes_written != static_cast(serialized_file.size())) { LOGE( "Failed to fully write to file: path = %s, " "bytes_written = %zd, bytes_attempted = %zu", path.c_str(), bytes_written, serialized_file.size()); return kFileWriteError; } LOGV("Successfully stored raw file: path = %s, size = %zu", path.c_str(), serialized_file.size()); return kNoError; } DeviceFiles::ResponseType DeviceFiles::RetrieveHashedFile( const std::string& name, video_widevine_client::sdk::File* deserialized_file) { std::string serialized_file; if (deserialized_file == nullptr) { LOGE("File handle parameter |deserialized_file| not provided"); return kParameterNull; } std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return kBasePathUnavailable; } path += name; if (!file_system_->Exists(path)) { LOGW("File does not exist: path = %s", path.c_str()); return kFileNotFound; } const ssize_t file_size = file_system_->FileSize(path); if (file_size <= 0) { LOGE("File size is invalid: %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kInvalidFileSize; } auto file = file_system_->Open(path, wvutil::FileSystem::kReadOnly); if (!file) { return kFileOpenFailed; } std::string serialized_hash_file; serialized_hash_file.resize(file_size); const ssize_t bytes_read = file->Read(&serialized_hash_file[0], serialized_hash_file.size()); if (bytes_read != file_size) { if (bytes_read < 0) { LOGE("Failed to read from file: path = %s", path.c_str()); } else { LOGE( "Failed to fully read from file: " "path = %s, bytes_read = %zd, bytes_attempted = %zd", path.c_str(), bytes_read, file_size); } // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileReadError; } LOGV("Successfully read file: path = %s, size = %zu", path.c_str(), serialized_hash_file.size()); HashedFile hash_file; if (!hash_file.ParseFromString(serialized_hash_file)) { LOGE("Unable to parse hash file"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileParseError1; } std::string hash = Sha256Hash(hash_file.file()); if (hash != hash_file.hash()) { LOGE("File hash mismatch: path = %s", path.c_str()); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileHashMismatch; } if (!deserialized_file->ParseFromString(hash_file.file())) { LOGE("Unable to parse hashed file"); // Remove the corrupted file so the caller will not get the same error // when trying to access the file repeatedly, causing the system to stall. file_system_->Remove(path); return kFileParseError2; } return kNoError; } bool DeviceFiles::FileExists(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } path += name; return file_system_->Exists(path); } bool DeviceFiles::ListFiles(std::vector* names) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } return file_system_->List(path, names); } bool DeviceFiles::RemoveFile(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return false; } path += name; return file_system_->Remove(path); } ssize_t DeviceFiles::GetFileSize(const std::string& name) { std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGE("Unable to get base path"); return -1; } path += name; return file_system_->FileSize(path); } bool DeviceFiles::GetCertificateFileName(CertificateType certificate_type, std::string* certificate_file_name) { RETURN_FALSE_IF_NULL(certificate_file_name); switch (certificate_type) { case kCertificateDefault: *certificate_file_name = wvutil::kCertificateFileName; return true; case kCertificateLegacy: *certificate_file_name = wvutil::kLegacyCertificateFileName; return true; case kCertificateAtsc: *certificate_file_name = wvutil::kAtscCertificateFileName; return true; default: return false; } } bool DeviceFiles::GetOemCertificateFileName( std::string* certificate_file_name) { RETURN_FALSE_IF_NULL(certificate_file_name); *certificate_file_name = wvutil::kOemCertificateFileName; return true; } std::string DeviceFiles::GetUsageTableFileName() { return kUsageTableFileName; } std::string DeviceFiles::GetHlsAttributesFileNameExtension() { return kHlsAttributesFileNameExt; } std::string DeviceFiles::GetLicenseFileNameExtension() { return kLicenseFileNameExt; } std::string DeviceFiles::GetOkpInfoFileName() { return kOkpInfoFileName; } std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { return wvutil::Base64SafeEncode(Md5Hash(input)); } } // namespace wvcdm