252 lines
7.8 KiB
C++
252 lines
7.8 KiB
C++
/*
|
|
* 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
|