| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <utils/Log.h> |
| |
| #include <sys/stat.h> |
| #include <string> |
| |
| #include "DeviceFiles.h" |
| #include "protos/DeviceFiles.pb.h" |
| |
| #include <openssl/sha.h> |
| |
| // Protobuf generated classes. |
| using clearkeydrm::HashedFile; |
| using clearkeydrm::License; |
| using clearkeydrm::License_LicenseState_ACTIVE; |
| using clearkeydrm::License_LicenseState_RELEASING; |
| using clearkeydrm::OfflineFile; |
| |
| namespace { |
| const char kLicenseFileNameExt[] = ".lic"; |
| |
| bool Hash(const std::string& data, std::string* hash) { |
| if (!hash) return false; |
| |
| hash->resize(SHA256_DIGEST_LENGTH); |
| |
| const unsigned char* input = reinterpret_cast<const unsigned char*>(data.data()); |
| unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]); |
| SHA256(input, data.size(), output); |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace clearkeydrm { |
| |
| bool DeviceFiles::StoreLicense(const std::string& keySetId, LicenseState state, |
| const std::string& licenseResponse) { |
| OfflineFile file; |
| file.set_type(OfflineFile::LICENSE); |
| file.set_version(OfflineFile::VERSION_1); |
| |
| License* license = file.mutable_license(); |
| switch (state) { |
| case kLicenseStateActive: |
| license->set_state(License_LicenseState_ACTIVE); |
| license->set_license(licenseResponse); |
| break; |
| case kLicenseStateReleasing: |
| license->set_state(License_LicenseState_RELEASING); |
| license->set_license(licenseResponse); |
| break; |
| default: |
| ALOGW("StoreLicense: Unknown license state: %u", state); |
| return false; |
| } |
| |
| std::string serializedFile; |
| file.SerializeToString(&serializedFile); |
| |
| return StoreFileWithHash(keySetId + kLicenseFileNameExt, serializedFile); |
| } |
| |
| bool DeviceFiles::StoreFileWithHash(const std::string& fileName, |
| const std::string& serializedFile) { |
| std::string hash; |
| if (!Hash(serializedFile, &hash)) { |
| ALOGE("StoreFileWithHash: Failed to compute hash"); |
| return false; |
| } |
| |
| HashedFile hashFile; |
| hashFile.set_file(serializedFile); |
| hashFile.set_hash(hash); |
| |
| std::string serializedHashFile; |
| hashFile.SerializeToString(&serializedHashFile); |
| |
| return StoreFileRaw(fileName, serializedHashFile); |
| } |
| |
| bool DeviceFiles::StoreFileRaw(const std::string& fileName, const std::string& serializedHashFile) { |
| MemoryFileSystem::MemoryFile memFile; |
| memFile.setFileName(fileName); |
| memFile.setContent(serializedHashFile); |
| memFile.setFileSize(serializedHashFile.size()); |
| size_t len = mFileHandle.Write(fileName, memFile); |
| |
| if (len != static_cast<size_t>(serializedHashFile.size())) { |
| ALOGE("StoreFileRaw: Failed to write %s", fileName.c_str()); |
| ALOGD("StoreFileRaw: expected=%zd, actual=%zu", serializedHashFile.size(), len); |
| return false; |
| } |
| |
| ALOGD("StoreFileRaw: wrote %zu bytes to %s", serializedHashFile.size(), fileName.c_str()); |
| return true; |
| } |
| |
| bool DeviceFiles::RetrieveLicense(const std::string& keySetId, LicenseState* state, |
| std::string* offlineLicense) { |
| OfflineFile file; |
| if (!RetrieveHashedFile(keySetId + kLicenseFileNameExt, &file)) { |
| return false; |
| } |
| |
| if (file.type() != OfflineFile::LICENSE) { |
| ALOGE("RetrieveLicense: Invalid file type"); |
| return false; |
| } |
| |
| if (file.version() != OfflineFile::VERSION_1) { |
| ALOGE("RetrieveLicense: Invalid file version"); |
| return false; |
| } |
| |
| if (!file.has_license()) { |
| ALOGE("RetrieveLicense: License not present"); |
| return false; |
| } |
| |
| License license = file.license(); |
| switch (license.state()) { |
| case License_LicenseState_ACTIVE: |
| *state = kLicenseStateActive; |
| break; |
| case License_LicenseState_RELEASING: |
| *state = kLicenseStateReleasing; |
| break; |
| default: |
| ALOGW("RetrieveLicense: Unrecognized license state: %u", kLicenseStateUnknown); |
| *state = kLicenseStateUnknown; |
| break; |
| } |
| *offlineLicense = license.license(); |
| return true; |
| } |
| |
| bool DeviceFiles::DeleteLicense(const std::string& keySetId) { |
| return mFileHandle.RemoveFile(keySetId + kLicenseFileNameExt); |
| } |
| |
| bool DeviceFiles::DeleteAllLicenses() { |
| return mFileHandle.RemoveAllFiles(); |
| } |
| |
| bool DeviceFiles::LicenseExists(const std::string& keySetId) { |
| return mFileHandle.FileExists(keySetId + kLicenseFileNameExt); |
| } |
| |
| std::vector<std::string> DeviceFiles::ListLicenses() const { |
| std::vector<std::string> licenses = mFileHandle.ListFiles(); |
| for (size_t i = 0; i < licenses.size(); i++) { |
| std::string& license = licenses[i]; |
| license = license.substr(0, license.size() - strlen(kLicenseFileNameExt)); |
| } |
| return licenses; |
| } |
| |
| bool DeviceFiles::RetrieveHashedFile(const std::string& fileName, OfflineFile* deSerializedFile) { |
| if (!deSerializedFile) { |
| ALOGE("RetrieveHashedFile: invalid file parameter"); |
| return false; |
| } |
| |
| if (!FileExists(fileName)) { |
| ALOGE("RetrieveHashedFile: %s does not exist", fileName.c_str()); |
| return false; |
| } |
| |
| ssize_t bytes = GetFileSize(fileName); |
| if (bytes <= 0) { |
| ALOGE("RetrieveHashedFile: invalid file size: %s", fileName.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. |
| RemoveFile(fileName); |
| return false; |
| } |
| |
| std::string serializedHashFile; |
| serializedHashFile.resize(bytes); |
| bytes = mFileHandle.Read(fileName, &serializedHashFile); |
| |
| if (bytes != static_cast<ssize_t>(serializedHashFile.size())) { |
| ALOGE("RetrieveHashedFile: Failed to read from %s", fileName.c_str()); |
| ALOGV("RetrieveHashedFile: expected: %zd, actual: %zd", serializedHashFile.size(), bytes); |
| // 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. |
| RemoveFile(fileName); |
| return false; |
| } |
| |
| ALOGV("RetrieveHashedFile: read %zd from %s", bytes, fileName.c_str()); |
| |
| HashedFile hashFile; |
| if (!hashFile.ParseFromString(serializedHashFile)) { |
| ALOGE("RetrieveHashedFile: Unable to parse hash file"); |
| // Remove corrupt file. |
| RemoveFile(fileName); |
| return false; |
| } |
| |
| std::string hash; |
| if (!Hash(hashFile.file(), &hash)) { |
| ALOGE("RetrieveHashedFile: Hash computation failed"); |
| return false; |
| } |
| |
| if (hash != hashFile.hash()) { |
| ALOGE("RetrieveHashedFile: Hash mismatch"); |
| // Remove corrupt file. |
| RemoveFile(fileName); |
| return false; |
| } |
| |
| if (!deSerializedFile->ParseFromString(hashFile.file())) { |
| ALOGE("RetrieveHashedFile: Unable to parse file"); |
| // Remove corrupt file. |
| RemoveFile(fileName); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool DeviceFiles::FileExists(const std::string& fileName) const { |
| return mFileHandle.FileExists(fileName); |
| } |
| |
| bool DeviceFiles::RemoveFile(const std::string& fileName) { |
| return mFileHandle.RemoveFile(fileName); |
| } |
| |
| ssize_t DeviceFiles::GetFileSize(const std::string& fileName) const { |
| return mFileHandle.GetFileSize(fileName); |
| } |
| |
| } // namespace clearkeydrm |