blob: f6f2aca1335fba9cf75e0906038d176783cc122e [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "biod/biod_storage.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <algorithm>
#include <sstream>
#include <utility>
#include <base/base64.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/important_file_writer.h>
#include <base/guid.h>
#include <base/json/json_reader.h>
#include <base/json/json_string_value_serializer.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <brillo/scoped_umask.h>
#include "biod/biometrics_manager_record.h"
#include "biod/utils.h"
namespace biod {
using base::FilePath;
namespace {
constexpr char kDaemonStorePath[] = "/run/daemon-store";
constexpr char kRecordFileName[] = "Record";
constexpr char kBiod[] = "biod";
// Members of the JSON file.
constexpr char kBioManagerMember[] = "biomanager";
constexpr char kData[] = "data";
constexpr char kLabel[] = "label";
constexpr char kRecordId[] = "record_id";
constexpr char kValidationVal[] = "match_validation_value";
constexpr char kVersionMember[] = "version";
} // namespace
BiodStorage::BiodStorage(const std::string& biometrics_manager_name)
: root_path_(kDaemonStorePath),
biometrics_manager_name_(biometrics_manager_name),
allow_access_(false) {}
void BiodStorage::SetRootPathForTesting(const base::FilePath& root_path) {
root_path_ = root_path;
}
bool BiodStorage::WriteRecord(
const BiodStorageInterface::RecordMetadata& record_metadata,
base::Value data) {
if (!allow_access_) {
LOG(ERROR) << "Access to the storage mounts not allowed.";
return false;
}
if (!record_metadata.IsValidUTF8()) {
LOG(ERROR) << "Record contains invalid UTF8.";
return false;
}
const std::string& record_id(record_metadata.record_id);
base::Value record_value(base::Value::Type::DICTIONARY);
record_value.SetStringKey(kLabel, record_metadata.label);
record_value.SetStringKey(kRecordId, record_id);
record_value.SetStringKey(kValidationVal,
record_metadata.GetValidationValBase64());
record_value.SetIntKey(kVersionMember, kRecordFormatVersion);
record_value.SetKey(kData, std::move(data));
record_value.SetStringKey(kBioManagerMember, biometrics_manager_name_);
std::string json_string;
JSONStringValueSerializer json_serializer(&json_string);
if (!json_serializer.Serialize(record_value)) {
LOG(ERROR) << "Failed to serialize record " << LogSafeID(record_id)
<< " to JSON.";
return false;
}
FilePath record_storage_filename = GetRecordFilename(record_metadata);
if (record_storage_filename.empty()) {
LOG(ERROR) << "Unable to get filename for record.";
return false;
}
{
brillo::ScopedUmask owner_only_umask(~(0700));
if (!base::CreateDirectory(record_storage_filename.DirName())) {
PLOG(ERROR) << "Cannot create directory for user "
<< LogSafeID(record_metadata.user_id) << ".";
return false;
}
}
{
brillo::ScopedUmask owner_only_umask(~(0600));
if (!base::ImportantFileWriter::WriteFileAtomically(record_storage_filename,
json_string)) {
LOG(ERROR) << "Failed to write JSON file for record "
<< LogSafeID(record_metadata.record_id) << ".";
return false;
}
}
LOG(INFO) << "Done writing record " << LogSafeID(record_id)
<< " to file successfully. ";
return true;
}
std::unique_ptr<std::vector<uint8_t>>
BiodStorage::ReadValidationValueFromRecord(const base::Value& record_dictionary,
const FilePath& record_path) {
std::string validation_val_str;
const std::string* validation_val_str_ptr =
record_dictionary.FindStringKey(kValidationVal);
if (!validation_val_str_ptr) {
LOG(WARNING) << "Cannot read validation value from " << record_path.value()
<< ".";
return nullptr;
}
validation_val_str = *validation_val_str_ptr;
if (!base::Base64Decode(validation_val_str, &validation_val_str)) {
LOG(ERROR) << "Unable to base64 decode validation value from "
<< record_path.value() << ".";
return nullptr;
}
return std::make_unique<std::vector<uint8_t>>(validation_val_str.begin(),
validation_val_str.end());
}
std::vector<BiodStorageInterface::Record> BiodStorage::ReadRecords(
const std::unordered_set<std::string>& user_ids) {
std::vector<BiodStorageInterface::Record> ret;
for (const auto& user_id : user_ids) {
auto result = ReadRecordsForSingleUser(user_id);
ret.insert(ret.end(), std::make_move_iterator(result.begin()),
std::make_move_iterator(result.end()));
}
return ret;
}
std::vector<BiodStorageInterface::Record> BiodStorage::ReadRecordsForSingleUser(
const std::string& user_id) {
std::vector<BiodStorageInterface::Record> ret;
if (!allow_access_) {
LOG(ERROR) << "Access to the storage mounts not yet allowed.";
return ret;
}
FilePath biod_path =
root_path_.Append(kBiod).Append(user_id).Append(biometrics_manager_name_);
base::FileEnumerator enum_records(biod_path, false,
base::FileEnumerator::FILES, "Record*");
for (FilePath record_path = enum_records.Next(); !record_path.empty();
record_path = enum_records.Next()) {
auto record = ReadRecordFromPath(record_path);
// In this function we are enumerating files, so if Optional returned
// by ReadRecordFromPath is empty, then there is something wrong with biod.
CHECK(record);
record->metadata.user_id = user_id;
ret.emplace_back(std::move(*record));
}
return ret;
}
base::Optional<BiodStorageInterface::Record> BiodStorage::ReadSingleRecord(
const std::string& user_id, const std::string& record_id) {
if (!allow_access_) {
LOG(ERROR) << "Access to the storage mounts not yet allowed.";
return base::nullopt;
}
base::FilePath record_path = root_path_.Append(kBiod)
.Append(user_id)
.Append(biometrics_manager_name_)
.Append(kRecordFileName + record_id);
auto record = ReadRecordFromPath(record_path);
if (record) {
record->metadata.user_id = user_id;
}
return record;
}
base::Optional<BiodStorageInterface::Record> BiodStorage::ReadRecordFromPath(
const base::FilePath& record_path) {
std::string json_string;
if (!base::ReadFileToString(record_path, &json_string)) {
LOG(ERROR) << "Failed to read the string from " << record_path.value()
<< ".";
// Biod can't find this file.
return base::nullopt;
}
// File was found. Return Record (valid or invalid) to indicate that it
// exists.
BiodStorageInterface::Record record;
record.valid = false;
// Get RecordId from path. In case of mismatch this RecordId is more
// important because it allows upper layers to remove invalid record
// properly.
std::string record_id_path = record_path.BaseName().value();
record_id_path.erase(0, sizeof(kRecordFileName) - 1);
record.metadata.record_id = record_id_path;
auto record_value = base::JSONReader::ReadAndReturnValueWithError(
json_string, base::JSON_ALLOW_TRAILING_COMMAS);
if (!record_value.value) {
LOG_IF(ERROR, !record_value.error_message.empty())
<< "JSON error message: " << record_value.error_message << ".";
return record;
}
if (!record_value.value->is_dict()) {
LOG(ERROR) << "Value " << record_path.value() << " is not a dictionary.";
return record;
}
base::Value record_dictionary = std::move(*record_value.value);
const std::string* record_id = record_dictionary.FindStringKey(kRecordId);
if (!record_id) {
LOG(ERROR) << "Cannot read record id from " << record_path.value() << ".";
return record;
}
// If RecordId from path is different than stored in the file then
// record is not valid.
if (record.metadata.record_id != *record_id) {
LOG(ERROR) << "RecordId from path " << LogSafeID(record.metadata.record_id)
<< " is different than RecordId stored in file "
<< LogSafeID(*record_id);
return record;
}
const std::string* label = record_dictionary.FindStringKey(kLabel);
if (!label) {
LOG(ERROR) << "Cannot read label from " << record_path.value() << ".";
return record;
}
record.metadata.label = *label;
base::Optional<int> record_format_version =
record_dictionary.FindIntKey(kVersionMember);
if (!record_format_version.has_value()) {
LOG(ERROR) << "Cannot read record format version from "
<< record_path.value() << ".";
return record;
}
record.metadata.record_format_version = *record_format_version;
if (*record_format_version < 0 ||
*record_format_version > kRecordFormatVersion) {
LOG(ERROR) << "Invalid format version from record " << record_path.value()
<< ".";
return record;
}
std::unique_ptr<std::vector<uint8_t>> validation_val =
ReadValidationValueFromRecord(record_dictionary, record_path);
// Validation value was introduced in format version 2, so it might not be
// present in older records.
if (!validation_val) {
if (*record_format_version >= kRecordFormatVersion) {
return record;
}
// If format version is older than 2, then it is valid old record (without
// validation value).
validation_val = std::make_unique<std::vector<uint8_t>>();
}
record.metadata.validation_val = *validation_val;
const base::Value* data = record_dictionary.FindKey(kData);
if (!data) {
LOG(ERROR) << "Cannot read data from " << record_path.value() << ".";
return record;
}
record.data = data->GetString();
record.valid = true;
return record;
}
bool BiodStorage::DeleteRecord(const std::string& user_id,
const std::string& record_id) {
if (!allow_access_) {
LOG(ERROR) << "Access to the storage mounts not yet allowed.";
return false;
}
FilePath record_storage_filename = root_path_.Append(kBiod)
.Append(user_id)
.Append(biometrics_manager_name_)
.Append(kRecordFileName + record_id);
if (!base::PathExists(record_storage_filename)) {
LOG(INFO) << "Trying to delete record " << LogSafeID(record_id)
<< " which does not exist on disk.";
return true;
}
if (!base::DeleteFile(record_storage_filename)) {
LOG(ERROR) << "Fail to delete record " << LogSafeID(record_id)
<< " from disk.";
return false;
}
LOG(INFO) << "Done deleting record " << LogSafeID(record_id) << " from disk.";
return true;
}
std::string BiodStorage::GenerateNewRecordId() {
std::string record_id(base::GenerateGUID());
// dbus member names only allow '_'
std::replace(record_id.begin(), record_id.end(), '-', '_');
return record_id;
}
base::FilePath BiodStorage::GetRecordFilename(
const BiodStorageInterface::RecordMetadata& record_metadata) {
std::vector<FilePath> paths = {
FilePath(kBiod), FilePath(record_metadata.user_id),
FilePath(biometrics_manager_name_),
FilePath(kRecordFileName + record_metadata.record_id)};
FilePath record_storage_filename = root_path_;
for (const auto& path : paths) {
if (path.IsAbsolute()) {
LOG(ERROR) << "Path component must not be absolute: '" << path << "'";
return base::FilePath();
}
record_storage_filename = record_storage_filename.Append(path);
}
return record_storage_filename;
}
} // namespace biod