blob: 4b8170931bab33e3fd1d98f089858c91729212e3 [file]
// Copyright (c) 2012 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 "chaps/object_store_impl.h"
#include <map>
#include <string>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/string_number_conversions.h>
#include <base/string_piece.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <chromeos/utility.h>
#include <leveldb/db.h>
#include <leveldb/env.h>
#include <leveldb/memenv.h>
#include "chaps/chaps_utility.h"
#include "pkcs11/cryptoki.h"
using std::map;
using std::string;
namespace chaps {
const char ObjectStoreImpl::kInternalBlobKeyPrefix[] = "InternalBlob";
const char ObjectStoreImpl::kPublicBlobKeyPrefix[] = "PublicBlob";
const char ObjectStoreImpl::kPrivateBlobKeyPrefix[] = "PrivateBlob";
const char ObjectStoreImpl::kBlobKeySeparator[] = "&";
const char ObjectStoreImpl::kDatabaseVersionKey[] = "DBVersion";
const char ObjectStoreImpl::kIDTrackerKey[] = "NextBlobID";
const int ObjectStoreImpl::kAESKeySizeBytes = 32;
const int ObjectStoreImpl::kHMACSizeBytes = 64;
const char ObjectStoreImpl::kDatabaseDirectory[] = "database";
const char ObjectStoreImpl::kObfuscationKey[] = {
'\x6f', '\xaa', '\x0a', '\xb6', '\x10', '\xc0', '\xa6', '\xe4', '\x07',
'\x8b', '\x05', '\x1c', '\xd2', '\x8b', '\xac', '\x2d', '\xba', '\x5e',
'\x14', '\x9c', '\xae', '\x57', '\xfb', '\x04', '\x13', '\x92', '\xc0',
'\x84', '\x2a', '\xea', '\xf6', '\xfb', '\0'};
ObjectStoreImpl::ObjectStoreImpl() {}
ObjectStoreImpl::~ObjectStoreImpl() {
// TODO(dkrahn): Use SecureBlob. See crosbug.com/27681.
chromeos::SecureMemset(const_cast<char*>(key_.data()), 0, key_.length());
}
bool ObjectStoreImpl::Init(const FilePath& database_path) {
LOG(INFO) << "Opening database in: " << database_path.value();
leveldb::Options options;
options.create_if_missing = true;
options.paranoid_checks = true;
if (database_path.value() == ":memory:") {
// Memory only environment, useful for testing.
LOG(INFO) << "Using memory-only environment.";
env_.reset(leveldb::NewMemEnv(leveldb::Env::Default()));
options.env = env_.get();
}
FilePath database_name = database_path.Append(kDatabaseDirectory);
leveldb::DB* db = NULL;
leveldb::Status status = leveldb::DB::Open(options,
database_name.value(),
&db);
if (!status.ok()) {
LOG(ERROR) << "Failed to open database: " << status.ToString();
// We don't want to risk using a database that has been corrupted.
status = leveldb::DestroyDB(database_name.value(), options);
if (!status.ok()) {
LOG(ERROR) << "Failed to destroy database: " << status.ToString();
return false;
}
LOG(WARNING) << "Recreating database from scratch.";
// Now retry the open.
status = leveldb::DB::Open(options, database_name.value(), &db);
if (!status.ok()) {
LOG(ERROR) << "Failed to open database again: " << status.ToString();
return false;
}
}
db_.reset(db);
int version = 0;
if (!ReadInt(kDatabaseVersionKey, &version)) {
if (!WriteInt(kIDTrackerKey, 1)) {
LOG(ERROR) << "Failed to initialize the blob ID tracker.";
return false;
}
if (!WriteInt(kDatabaseVersionKey, 1)) {
LOG(ERROR) << "Failed to initialize the database version.";
return false;
}
}
return true;
}
bool ObjectStoreImpl::GetInternalBlob(int blob_id, string* blob) {
if (!ReadBlob(CreateBlobKey(kInternal, blob_id), blob)) {
// Don't log this since it happens legitimately when a blob has not yet been
// set.
return false;
}
return true;
}
bool ObjectStoreImpl::SetInternalBlob(int blob_id, const string& blob) {
if (!WriteBlob(CreateBlobKey(kInternal, blob_id), blob)) {
LOG(ERROR) << "Failed to write internal blob: " << blob_id;
return false;
}
return true;
}
bool ObjectStoreImpl::SetEncryptionKey(const string& key) {
if (key.length() != static_cast<size_t>(kAESKeySizeBytes)) {
LOG(ERROR) << "Unexpected key size: " << key.length();
return false;
}
key_ = key;
return true;
}
bool ObjectStoreImpl::InsertObjectBlob(const ObjectBlob& blob,
int* handle) {
if (blob.is_private && key_.empty()) {
LOG(ERROR) << "The store encryption key has not been initialized.";
return false;
}
if (!GetNextID(handle)) {
LOG(ERROR) << "Failed to generate blob identifier.";
return false;
}
blob_type_map_[*handle] = blob.is_private ? kPrivate : kPublic;
return UpdateObjectBlob(*handle, blob);
}
bool ObjectStoreImpl::DeleteObjectBlob(int handle) {
leveldb::WriteOptions options;
options.sync = true;
string db_key = CreateBlobKey(GetBlobType(handle), handle);
leveldb::Status status = db_->Delete(options, db_key);
if (!status.ok()) {
LOG(ERROR) << "Failed to delete blob: " << status.ToString();
return false;
}
return true;
}
bool ObjectStoreImpl::UpdateObjectBlob(int handle, const ObjectBlob& blob) {
ObjectBlob encrypted_blob;
BlobType type = GetBlobType(handle);
if (blob.is_private != (type == kPrivate)) {
LOG(ERROR) << "Object privacy mismatch.";
return false;
}
if (!Encrypt(blob, &encrypted_blob)) {
LOG(ERROR) << "Failed to encrypt object blob.";
return false;
}
if (!WriteBlob(CreateBlobKey(type, handle), encrypted_blob.blob)) {
LOG(ERROR) << "Failed to write object blob.";
}
return true;
}
bool ObjectStoreImpl::LoadPublicObjectBlobs(map<int, ObjectBlob>* blobs) {
return LoadObjectBlobs(kPublic, blobs);
}
bool ObjectStoreImpl::LoadPrivateObjectBlobs(map<int, ObjectBlob>* blobs) {
if (key_.empty()) {
LOG(ERROR) << "The store encryption key has not been initialized.";
return false;
}
return LoadObjectBlobs(kPrivate, blobs);
}
bool ObjectStoreImpl::LoadObjectBlobs(BlobType type,
map<int, ObjectBlob>* blobs) {
scoped_ptr<leveldb::Iterator> it(db_->NewIterator(leveldb::ReadOptions()));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
BlobType it_type;
int id = 0;
if (ParseBlobKey(it->key().ToString(), &it_type, &id) && type == it_type) {
ObjectBlob encrypted_blob;
encrypted_blob.is_private = (type == kPrivate);
encrypted_blob.blob = it->value().ToString();
ObjectBlob blob;
if (!Decrypt(encrypted_blob, &blob)) {
LOG(WARNING) << "Failed to decrypt object blob.";
continue;
}
(*blobs)[id] = blob;
blob_type_map_[id] = type;
}
}
return true;
}
bool ObjectStoreImpl::Encrypt(const ObjectBlob& plain_text,
ObjectBlob* cipher_text) {
if (plain_text.is_private && key_.empty()) {
LOG(ERROR) << "The store encryption key has not been initialized.";
return false;
}
cipher_text->is_private = plain_text.is_private;
string key = plain_text.is_private ? key_ : kObfuscationKey;
// Append a MAC to the plain-text before encrypting.
return RunCipher(true,
key,
string(),
AppendHMAC(plain_text.blob),
&cipher_text->blob);
}
bool ObjectStoreImpl::Decrypt(const ObjectBlob& cipher_text,
ObjectBlob* plain_text) {
if (cipher_text.is_private && key_.empty()) {
LOG(ERROR) << "The store encryption key has not been initialized.";
return false;
}
plain_text->is_private = cipher_text.is_private;
// Recover the IV from the input.
string key = cipher_text.is_private ? key_ : kObfuscationKey;
string plain_text_with_hmac;
if (!RunCipher(false, key, string(), cipher_text.blob, &plain_text_with_hmac))
return false;
// Check the MAC that was appended before encrypting.
if (!VerifyAndStripHMAC(plain_text_with_hmac, &plain_text->blob))
return false;
return true;
}
string ObjectStoreImpl::AppendHMAC(const string& input) {
return input + HmacSha512(input, key_);
}
bool ObjectStoreImpl::VerifyAndStripHMAC(const string& input,
string* stripped) {
if (input.size() < static_cast<size_t>(kHMACSizeBytes)) {
LOG(ERROR) << "Failed to verify blob integrity.";
return false;
}
*stripped = input.substr(0, input.size() - kHMACSizeBytes);
string hmac = input.substr(input.size() - kHMACSizeBytes);
if (hmac != HmacSha512(*stripped, key_)) {
LOG(ERROR) << "Failed to verify blob integrity.";
return false;
}
return true;
}
string ObjectStoreImpl::CreateBlobKey(BlobType type, int blob_id) {
const char* prefix = NULL;
switch (type) {
case kInternal:
prefix = kInternalBlobKeyPrefix;
break;
case kPublic:
prefix = kPublicBlobKeyPrefix;
break;
case kPrivate:
prefix = kPrivateBlobKeyPrefix;
break;
default:
LOG(FATAL) << "Invalid enum value.";
}
return base::StringPrintf("%s%s%d", prefix, kBlobKeySeparator, blob_id);
}
bool ObjectStoreImpl::ParseBlobKey(const string& key,
BlobType* type,
int* blob_id) {
size_t index = key.rfind(kBlobKeySeparator);
if (index == string::npos) {
// This isn't a blob.
return false;
}
base::StringPiece key_piece(key.begin() + (index + 1), key.end());
if (!base::StringToInt(key_piece, blob_id)) {
LOG(ERROR) << "Invalid blob key id: " << key;
return false;
}
string prefix = key.substr(0, index);
if (prefix == kInternalBlobKeyPrefix) {
*type = kInternal;
} else if (prefix == kPublicBlobKeyPrefix) {
*type = kPublic;
} else if (prefix == kPrivateBlobKeyPrefix) {
*type = kPrivate;
} else {
LOG(ERROR) << "Invalid blob key prefix: " << key;
return false;
}
return true;
}
bool ObjectStoreImpl::GetNextID(int* next_id) {
if (!ReadInt(kIDTrackerKey, next_id)) {
LOG(ERROR) << "Failed to read ID tracker.";
return false;
}
if (*next_id == INT_MAX) {
LOG(ERROR) << "Object ID overflow.";
return false;
}
if (!WriteInt(kIDTrackerKey, *next_id + 1)) {
LOG(ERROR) << "Failed to write ID tracker.";
return false;
}
return true;
}
bool ObjectStoreImpl::ReadBlob(const string& key, string* value) {
leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, value);
if (!status.ok()) {
if (!status.IsNotFound())
LOG(ERROR) << "Failed to read value from database: " << status.ToString();
return false;
}
return true;
}
bool ObjectStoreImpl::ReadInt(const string& key, int* value) {
string value_string;
if (!ReadBlob(key, &value_string))
return false;
if (!base::StringToInt(value_string, value)) {
LOG(ERROR) << "Invalid integer value.";
return false;
}
return true;
}
bool ObjectStoreImpl::WriteBlob(const string& key, const string& value) {
leveldb::WriteOptions options;
options.sync = true;
leveldb::Status status = db_->Put(options, key, value);
if (!status.ok()) {
LOG(ERROR) << "Failed to write value to database: " << status.ToString();
return false;
}
return true;
}
bool ObjectStoreImpl::WriteInt(const string& key, int value) {
return WriteBlob(key, base::IntToString(value));
}
ObjectStoreImpl::BlobType ObjectStoreImpl::GetBlobType(int blob_id) {
map<int, BlobType>::iterator it = blob_type_map_.find(blob_id);
if (it == blob_type_map_.end())
return kInternal;
return it->second;
}
} // namespace chaps