From c6063a449904eded64391bccfbce37e53475a1f7 Mon Sep 17 00:00:00 2001 From: Matthew Von-Maszewski Date: Thu, 28 May 2020 16:26:30 -0400 Subject: [PATCH] Feature: AES CTR encryption (#6) * mostly compiling version of env_encrypt_2.h ... one std::move error ... fix next * cleans up build (must add export EXTRA_LDFLAGS=-lcrypto -lssl manually) * hmm, somebody messed with SYNC_POINT defines ... and made debug unbuildable ... unit tests too * Add the original encryption Env to the env_basic_test unit test suite. * I lied. Forced to clean up Java style code in env_encryption by moving declaration half to env_encryption.h to be able to unit test new OpenSSL encryption. What a pain. * Going back to original layout. Rebuilt env_encryption.h/.cc into proper C++ declaration / definition split to allow proper inheritance (resuse). * first batch of tests, other than one for file size, work with new encryption code that does not yet encrypt (infrastructure validation). * ok, GetFileSize() corrected (slow but corrected). Considering whether or not to do same for GetChildrenFileAttributes * and add GetChildrenFileAttributes() update * Activate AESBlockAccessCipherStream. Remove dead code from original copying of env_encryption.cc. Unit test works. * change unique_ptr with deleter to traditional pointer code. Circle build did not want to compile it. And add some error checking to EncryptBlock() * use EVP_MD_CTX_create/destroy instead of new/free. This is openssl 1.0 syntax that is compatible in openssl 1.1 * move the definition of Sha1Description_t(std::string &) to .cc file in hopes of eliminating link issues in starrocks unit tests * again move some AES stuff from .h to .cc * add helper constructor to AesCtrKey_t (NOT TESTED). add IsValid() to Sha1Description and AesCtrKey_t * attempt to make initialization easier with Sha1Description_t as const. * const was a really bad idea * need explicit copy constructor with move disabled * removed delete of move constructor ... removal seems suspect * add env_encrypt_2_test to builds. Test Sha1Description_t. * saving for safety. first NIST AES case matches. code is in hack state. will clean and add other cases tomorrow. * code clean up within EncryptBlock. push to see if circle compiles * add remaining NIST cases. * added operator== for unit testing * remove non-portable byteswap.h ... not using it anymore ... and breaks OSX build * attempt include fix for osx * make openssl dependency OS specific * backport files used in Facebook/rocksdb PR * rename our env_encrypt_2 to more rocksdb-like env_encrypt2 * create conditional build of EnvEncrypt2 based on flag ROCKSDB_OPENSSL_AES_CTR * linux library loader code. not integrated. not yet supporting OSX * Linux library load ready for libcrypto SHA1 and RAND functions (includes unit tests). AES CTR functions next. * Add remaining functions from libcrypto that are used in EncryptedEnv2 * slight change to have .dylib names instead of .so names on OSX build * Create EncryptedEnv2::WriteKey_t and ReadKeys_t to simplify look of code. * create EncryptedEnv2::Default() to help time static loading of libcrypto. * clang-format applied * remove conditional openssl from OSX build * hmm, missed removing include files for openssl * Revert "hmm, missed removing include files for openssl" This reverts commit e22a1f63cb0e6a6ca8ca44621284ff9aad3c2221. * Revert "remove conditional openssl from OSX build" This reverts commit 4eef8d4161ccf63d1856f238389f1f54a4d9d814. * address PR comments from Alex Co-authored-by: matthewvon Co-authored-by: MatthewVon --- CMakeLists.txt | 3 + Makefile | 8 + TARGETS | 12 + env/BUILD | 12 + env/env_basic_test.cc | 32 +- env/env_encrypt2.cc | 558 ++++++++++++++++++++++++ env/env_encrypt2_test.cc | 704 +++++++++++++++++++++++++++++++ env/env_encryption.cc | 602 ++++++++++++++------------ include/BUILD | 12 + include/rocksdb/env_encrypt2.h | 469 ++++++++++++++++++++ include/rocksdb/env_encryption.h | 508 ++++++++++++++++------ include/rocksdb/perf_context.h | 6 + port/BUILD | 2 + src.mk | 4 + util/BUILD | 8 + util/build_version.cc | 4 +- util/library_loader.cc | 114 +++++ util/library_loader.h | 169 ++++++++ util/library_loader_test.cc | 87 ++++ util/sync_point.h | 8 +- 20 files changed, 2890 insertions(+), 432 deletions(-) create mode 100644 env/env_encrypt2.cc create mode 100644 env/env_encrypt2_test.cc create mode 100644 include/rocksdb/env_encrypt2.h create mode 100644 util/library_loader.cc create mode 100644 util/library_loader.h create mode 100644 util/library_loader_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 85c59064e..92011100e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -522,6 +522,7 @@ set(SOURCES env/env.cc env/env_chroot.cc env/env_encryption.cc + env/env_encrypt2.cc env/env_hdfs.cc env/mock_env.cc memtable/alloc_tracker.cc @@ -918,6 +919,7 @@ if(WITH_TESTS) db/write_callback_test.cc db/write_controller_test.cc env/env_basic_test.cc + env/env_encrypt2_test.cc env/env_test.cc env/mock_env_test.cc memtable/inlineskiplist_test.cc @@ -956,6 +958,7 @@ if(WITH_TESTS) util/hash_test.cc util/heap_test.cc util/rate_limiter_test.cc + util/library_loader_test.cc util/repeatable_thread_test.cc util/slice_transform_test.cc util/timer_queue_test.cc diff --git a/Makefile b/Makefile index 09e2cd3ea..b6b0abf46 100644 --- a/Makefile +++ b/Makefile @@ -419,8 +419,10 @@ TESTS = \ coding_test \ inlineskiplist_test \ env_basic_test \ + env_encrypt2_test \ env_test \ hash_test \ + library_loader_test \ thread_local_test \ rate_limiter_test \ perf_context_test \ @@ -1135,6 +1137,9 @@ coding_test: util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS) hash_test: util/hash_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +library_loader_test: util/library_loader_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + option_change_migration_test: utilities/option_change_migration/option_change_migration_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) @@ -1306,6 +1311,9 @@ sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(L spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) +env_encrypt2_test: env/env_encrypt2_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(AM_LINK) + env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS) $(AM_LINK) diff --git a/TARGETS b/TARGETS index 43f8bd5b2..8a34df4ac 100644 --- a/TARGETS +++ b/TARGETS @@ -139,6 +139,7 @@ cpp_library( "env/env.cc", "env/env_chroot.cc", "env/env_encryption.cc", + "env/env_encrypt2.cc", "env/env_hdfs.cc", "env/env_posix.cc", "env/io_posix.cc", @@ -227,6 +228,7 @@ cpp_library( "util/filter_policy.cc", "util/hash.cc", "util/jemalloc_nodump_allocator.cc", + "util/library_loader.cc", "util/log_buffer.cc", "util/murmurhash.cc", "util/random.cc", @@ -702,6 +704,11 @@ ROCKS_TESTS = [ "env/env_basic_test.cc", "serial", ], + [ + "env_encrypt2_test", + "env/env_encrypt2_test.cc", + "serial", + ], [ "env_test", "env/env_test.cc", @@ -812,6 +819,11 @@ ROCKS_TESTS = [ "tools/ldb_cmd_test.cc", "serial", ], + [ + "library_loader_test", + "util/library_loader_test.cc", + "serial", + ], [ "listener_test", "db/listener_test.cc", diff --git a/env/BUILD b/env/BUILD index 55d49c55f..22176e63d 100644 --- a/env/BUILD +++ b/env/BUILD @@ -19,6 +19,7 @@ PLATFORM_HDRS = select({ COMMON_SRCS = [ "env.cc", "env_encryption.cc", + "env_encrypt2.cc", ] PLATFORM_SRCS = select({ @@ -100,6 +101,17 @@ constrained_test( ], ) +constrained_test( + name = "env_encrypt2_test", + srcs = ["env_encrypt2_test.cc"], + deps = [ + ":env", + ":test_utils", + "//util", + "//util:test_utils", + ], +) + constrained_test( name = "env_test", srcs = ["env_test.cc"], diff --git a/env/env_basic_test.cc b/env/env_basic_test.cc index 22983dbec..42a6a98ef 100644 --- a/env/env_basic_test.cc +++ b/env/env_basic_test.cc @@ -1,17 +1,19 @@ // Copyright (c) 2011 The LevelDB Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. See the AUTHORS file for names of contributors. +// +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#include #include #include #include -#include #include "env/mock_env.h" #include "rocksdb/env.h" +#include "rocksdb/env_encryption.h" #include "rocksdb/utilities/object_registry.h" #include "util/testharness.h" - namespace rocksdb { // Normalizes trivial differences across Envs such that these test cases can @@ -21,8 +23,8 @@ class NormalizingEnvWrapper : public EnvWrapper { explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {} // Removes . and .. from directory listing - virtual Status GetChildren(const std::string& dir, - std::vector* result) override { + Status GetChildren(const std::string& dir, + std::vector* result) override { Status status = EnvWrapper::GetChildren(dir, result); if (status.ok()) { result->erase(std::remove_if(result->begin(), result->end(), @@ -35,7 +37,7 @@ class NormalizingEnvWrapper : public EnvWrapper { } // Removes . and .. from directory listing - virtual Status GetChildrenFileAttributes( + Status GetChildrenFileAttributes( const std::string& dir, std::vector* result) override { Status status = EnvWrapper::GetChildrenFileAttributes(dir, result); if (status.ok()) { @@ -60,11 +62,9 @@ class EnvBasicTestWithParam : public testing::Test, test_dir_ = test::PerThreadDBPath(env_, "env_basic_test"); } - void SetUp() { - env_->CreateDirIfMissing(test_dir_); - } + void SetUp() override { env_->CreateDirIfMissing(test_dir_); } - void TearDown() { + void TearDown() override { std::vector files; env_->GetChildren(test_dir_, &files); for (const auto& file : files) { @@ -90,6 +90,19 @@ INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam, static std::unique_ptr mock_env(new MockEnv(Env::Default())); INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam, ::testing::Values(mock_env.get())); + +// next statements run env test against default encryption code. +static ROT13BlockCipher encrypt_block_rot13(32); + +static CTREncryptionProvider encrypt_provider_ctr(encrypt_block_rot13); + +static std::unique_ptr encrypt_env(new NormalizingEnvWrapper( + NewEncryptedEnv(Env::Default(), &encrypt_provider_ctr))); +INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvBasicTestWithParam, + ::testing::Values(encrypt_env.get())); +INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvMoreTestWithParam, + ::testing::Values(encrypt_env.get())); + #ifndef ROCKSDB_LITE static std::unique_ptr mem_env(NewMemEnv(Env::Default())); INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam, @@ -111,6 +124,7 @@ std::vector GetCustomEnvs() { const char* uri = getenv("TEST_ENV_URI"); if (uri != nullptr) { custom_env = NewCustomObject(uri, &custom_env_guard); +// Env::LoadEnv(uri, &custom_env); } } diff --git a/env/env_encrypt2.cc b/env/env_encrypt2.cc new file mode 100644 index 000000000..a2af1087d --- /dev/null +++ b/env/env_encrypt2.cc @@ -0,0 +1,558 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +// +// env_encryption.cc copied to this file then modified. + +#ifdef ROCKSDB_OPENSSL_AES_CTR +#ifndef ROCKSDB_LITE + +#include "rocksdb/env_encrypt2.h" + +#include +#include +#include + +#include "util/coding.h" +#include "util/random.h" + +#endif + +namespace rocksdb { + +// following define block from page 70: +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf +#if !defined(ALIGN16) +#if defined(__GNUC__) +#define ALIGN16 __attribute__((aligned(16))) +#else +#define ALIGN16 __declspec(align(16)) +#endif +#endif + +#ifndef ROCKSDB_LITE + +Sha1Description_t::Sha1Description_t(const std::string& key_desc_str) { + bool good = {true}; + int ret_val; + unsigned len; + + memset(desc, 0, EVP_MAX_MD_SIZE); + if (0 != key_desc_str.length() && EncryptedEnv2::crypto_.IsValid()) { + std::unique_ptr context( + EncryptedEnv2::crypto_.EVP_MD_CTX_new(), + EncryptedEnv2::crypto_.EVP_MD_CTX_free_ptr()); + + ret_val = EncryptedEnv2::crypto_.EVP_DigestInit_ex( + context.get(), EncryptedEnv2::crypto_.EVP_sha1(), nullptr); + good = (1 == ret_val); + if (good) { + ret_val = EncryptedEnv2::crypto_.EVP_DigestUpdate( + context.get(), key_desc_str.c_str(), key_desc_str.length()); + good = (1 == ret_val); + } + + if (good) { + ret_val = + EncryptedEnv2::crypto_.EVP_DigestFinal_ex(context.get(), desc, &len); + good = (1 == ret_val); + } + } else { + good = false; + } + + valid = good; +} + +AesCtrKey_t::AesCtrKey_t(const std::string& key_str) : valid(false) { + memset(key, 0, EVP_MAX_KEY_LENGTH); + + // simple parse: must be 64 characters long and hexadecimal values + if (64 == key_str.length()) { + auto bad_pos = key_str.find_first_not_of("abcdefABCDEF0123456789"); + if (std::string::npos == bad_pos) { + for (size_t idx = 0, idx2 = 0; idx < key_str.length(); idx += 2, ++idx2) { + std::string hex_string(key_str.substr(idx, 2)); + key[idx2] = std::stoul(hex_string, 0, 16); + } + valid = true; + } + } +} + +// +// AES_BLOCK_SIZE assumed to be 16 +// +typedef union { + uint64_t nums[2]; + uint8_t bytes[AES_BLOCK_SIZE]; +} AesAlignedBlock_t; + +Status AESBlockAccessCipherStream::EncryptBlock(uint64_t blockIndex, char* data, + char* /*scratch*/) { + // + // AES_BLOCK_SIZE assumed to be 16 + // + assert(AES_BLOCK_SIZE == 16); + assert(sizeof(AesAlignedBlock_t) == AES_BLOCK_SIZE); + + Status status; + ALIGN16 AesAlignedBlock_t block_in, block_out, iv; + int out_len = 0, in_len = {AES_BLOCK_SIZE}, ret_val; + + if (EncryptedEnv2::crypto_.IsValid()) { + std::unique_ptr context( + EncryptedEnv2::crypto_.EVP_CIPHER_CTX_new(), + EncryptedEnv2::crypto_.EVP_CIPHER_CTX_free_ptr()); + + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + memcpy(iv.bytes, nonce_, AES_BLOCK_SIZE / 2); + EncodeFixed64((char*)&iv.bytes[AES_BLOCK_SIZE / 2], + blockIndex); // this will be little endian + block_in.nums[0] = 0; + block_in.nums[1] = 0; + + ret_val = EncryptedEnv2::crypto_.EVP_EncryptInit_ex( + context.get(), EncryptedEnv2::crypto_.EVP_aes_256_ctr(), nullptr, + key_.key, iv.bytes); + if (1 == ret_val) { + ret_val = EncryptedEnv2::crypto_.EVP_EncryptUpdate( + context.get(), block_out.bytes, &out_len, block_in.bytes, in_len); + + if (1 != ret_val || AES_BLOCK_SIZE != out_len) { + status = Status::InvalidArgument("EVP_EncryptUpdate failed: ", + AES_BLOCK_SIZE == out_len + ? "bad return value" + : "output length short"); + } + } else { + status = Status::InvalidArgument("EVP_EncryptInit_ex failed."); + } + + // XOR data with ciphertext. + uint64_t* data_ptr; + data_ptr = (uint64_t*)data; + *data_ptr ^= block_out.nums[0]; + data_ptr = (uint64_t*)(data + 8); + *data_ptr ^= block_out.nums[1]; + } else { + status = Status::NotSupported( + "libcrypto not available for encryption/decryption."); + } + + return status; +} + +Status AESBlockAccessCipherStream::DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) { + return EncryptBlock(blockIndex, data, scratch); +} + +Status CTREncryptionProvider2::CreateNewPrefix(const std::string& /*fname*/, + char* prefix, + size_t prefixLength) { + Status s; + if (EncryptedEnv2::crypto_.IsValid()) { + if (sizeof(Prefix0_t) <= prefixLength) { + int ret_val; + + Prefix0_t* pf = {(Prefix0_t*)prefix}; + memcpy(pf->key_description_, key_desc_.desc, sizeof(key_desc_.desc)); + ret_val = EncryptedEnv2::crypto_.RAND_bytes( + (unsigned char*)&pf->nonce_, + AES_BLOCK_SIZE / 2); // RAND_poll() to initialize + if (1 != ret_val) { + s = Status::NotSupported("RAND_bytes failed"); + } + } else { + s = Status::NotSupported("Prefix size needs to be 28 or more"); + } + } else { + s = Status::NotSupported("RAND_bytes() from libcrypto not available."); + } + + return s; +} + +// Returns an Env that encrypts data when stored on disk and decrypts data when +// read from disk. +Env* NewEncryptedEnv2(Env* base_env, EncryptedEnv2::ReadKeys_t encrypt_read, + EncryptedEnv2::WriteKey_t encrypt_write) { + Env* ret_env{base_env}; + EncryptedEnv2* new_env{nullptr}; + + if (Env::Default() == base_env) { + // use safer static construction so libcrypto is synchronously loaded + new_env = + (EncryptedEnv2*)EncryptedEnv2::Default(encrypt_read, encrypt_write); + } else if (nullptr != base_env) { + new_env = new EncryptedEnv2(base_env, encrypt_read, encrypt_write); + } + + // warning, dynamic loading of libcrypto could be delayed ... making this + // false + if (nullptr != new_env && new_env->IsValid()) { + ret_env = new_env; + } + + return ret_env; +} + +EncryptedEnv2::EncryptedEnv2(Env* base_env, + EncryptedEnv2::ReadKeys_t encrypt_read, + EncryptedEnv2::WriteKey_t encrypt_write) + : EnvWrapper(base_env), + encrypt_read_(encrypt_read), + encrypt_write_(encrypt_write), + valid_(false) { + valid_ = crypto_.IsValid(); + + // warning, dynamic loading of libcrypto could be delayed ... making this + // false + if (IsValid()) { + crypto_.RAND_poll(); + } +} + +EncryptedEnv2::EncryptedEnv2(Env* base_env) + : EnvWrapper(base_env), valid_(false) {} + +void EncryptedEnv2::SetKeys(EncryptedEnv2::ReadKeys_t encrypt_read, + EncryptedEnv2::WriteKey_t encrypt_write) { + encrypt_read_ = encrypt_read; + encrypt_write_ = encrypt_write; + + valid_ = crypto_.IsValid(); + + if (IsValid()) { + crypto_.RAND_poll(); + } +} + +// NewSequentialFile opens a file for sequential reading. +Status EncryptedEnv2::NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) { + result->reset(); + if (options.use_mmap_reads) { + return Status::InvalidArgument(); + } + + // Open file using underlying Env implementation + std::unique_ptr underlying; + auto status = EnvWrapper::NewSequentialFile(fname, &underlying, options); + if (status.ok()) { + std::shared_ptr provider; + std::unique_ptr stream; + status = ReadSeqEncryptionPrefix(underlying.get(), provider, + stream); + + if (status.ok()) { + if (provider) { + (*result) = std::unique_ptr( + new EncryptedSequentialFile(underlying.release(), stream.release(), + provider->GetPrefixLength())); + + } else { + // normal file, not encrypted + // sequential file might not allow backing up to begining, close and + // reopen + underlying.reset(nullptr); + status = EnvWrapper::NewSequentialFile(fname, result, options); + } + } + } + + return status; +} + +// NewRandomAccessFile opens a file for random read access. +Status EncryptedEnv2::NewRandomAccessFile( + const std::string& fname, std::unique_ptr* result, + const EnvOptions& options) { + result->reset(); + if (options.use_mmap_reads) { + return Status::InvalidArgument(); + } + + // Open file using underlying Env implementation + std::unique_ptr underlying; + auto status = EnvWrapper::NewRandomAccessFile(fname, &underlying, options); + if (status.ok()) { + std::shared_ptr provider; + std::unique_ptr stream; + status = ReadRandEncryptionPrefix(underlying.get(), + provider, stream); + + if (status.ok()) { + if (provider) { + (*result) = + std::unique_ptr(new EncryptedRandomAccessFile( + underlying.release(), stream.release(), + provider->GetPrefixLength())); + + } else { + // normal file, not encrypted + (*result).reset(underlying.release()); + } + } + } + return status; +} + +// NewWritableFile opens a file for sequential writing. +Status EncryptedEnv2::NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) { + Status status; + result->reset(); + + if (!options.use_mmap_writes) { + // Open file using underlying Env implementation + std::unique_ptr underlying; + status = EnvWrapper::NewWritableFile(fname, &underlying, options); + + if (status.ok()) { + if (IsWriteEncrypted()) { + std::unique_ptr stream; + + status = WriteSeqEncryptionPrefix(underlying.get(), stream); + + if (status.ok()) { + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), + encrypt_write_.second->GetPrefixLength())); + } + } else { + (*result).reset(underlying.release()); + } + } + } else { + status = Status::InvalidArgument(); + } + + return status; +} + +// Create an object that writes to a new file with the specified +// name. Deletes any existing file with the same name and creates a +// new file. On success, stores a pointer to the new file in +// *result and returns OK. On failure stores nullptr in *result and +// returns non-OK. +// +// The returned file will only be accessed by one thread at a time. +Status EncryptedEnv2::ReopenWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) { + Status status; + result->reset(); + + if (!options.use_mmap_writes) { + // Open file using underlying Env implementation + std::unique_ptr underlying; + status = EnvWrapper::ReopenWritableFile(fname, &underlying, options); + + if (status.ok()) { + if (IsWriteEncrypted()) { + std::unique_ptr stream; + + status = WriteSeqEncryptionPrefix(underlying.get(), stream); + + if (status.ok()) { + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), + encrypt_write_.second->GetPrefixLength())); + } + } else { + (*result).reset(underlying.release()); + } + } + } else { + status = Status::InvalidArgument(); + } + + return status; +} + +// Reuse an existing file by renaming it and opening it as writable. +Status EncryptedEnv2::ReuseWritableFile(const std::string& fname, + const std::string& old_fname, + std::unique_ptr* result, + const EnvOptions& options) { + Status status; + result->reset(); + + if (!options.use_mmap_writes) { + // Open file using underlying Env implementation + std::unique_ptr underlying; + status = + EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options); + + if (status.ok()) { + if (IsWriteEncrypted()) { + std::unique_ptr stream; + + status = WriteSeqEncryptionPrefix(underlying.get(), stream); + + if (status.ok()) { + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), + encrypt_write_.second->GetPrefixLength())); + } + } else { + (*result).reset(underlying.release()); + } + } + } else { + status = Status::InvalidArgument(); + } + + return status; +} + +// Open `fname` for random read and write, if file doesn't exist the file +// will be created. On success, stores a pointer to the new file in +// *result and returns OK. On failure returns non-OK. +// +// The returned file will only be accessed by one thread at a time. +Status EncryptedEnv2::NewRandomRWFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) { + Status status; + result->reset(); + + // Check file exists + bool isNewFile = !FileExists(fname).ok(); + + if (!options.use_mmap_writes && !options.use_mmap_reads) { + // Open file using underlying Env implementation + std::unique_ptr underlying; + status = EnvWrapper::NewRandomRWFile(fname, &underlying, options); + + if (status.ok()) { + std::shared_ptr provider; + std::unique_ptr stream; + + if (!isNewFile) { + // file exists, get existing crypto info + status = ReadRandEncryptionPrefix(underlying.get(), + provider, stream); + + } else { + // new file + if (IsWriteEncrypted()) { + status = WriteRandEncryptionPrefix(underlying.get(), stream); + provider = encrypt_write_.second; + } + } + + // establish encrypt or not, finalize file object + if (status.ok()) { + if (provider) { + (*result) = std::unique_ptr( + new EncryptedRandomRWFile(underlying.release(), stream.release(), + provider->GetPrefixLength())); + } else { + (*result).reset(underlying.release()); + } + } + } + } else { + status = Status::InvalidArgument(); + } + + return status; +} + +// Store in *result the attributes of the children of the specified directory. +// In case the implementation lists the directory prior to iterating the files +// and files are concurrently deleted, the deleted files will be omitted from +// result. +// The name attributes are relative to "dir". +// Original contents of *results are dropped. +// Returns OK if "dir" exists and "*result" contains its children. +// NotFound if "dir" does not exist, the calling process does not have +// permission to access "dir", or if "dir" is invalid. +// IOError if an IO Error was encountered +Status EncryptedEnv2::GetChildrenFileAttributes( + const std::string& dir, std::vector* result) { + auto status = EnvWrapper::GetChildrenFileAttributes(dir, result); + if (status.ok()) { + // this is slightly expensive, but fortunately not used heavily + std::shared_ptr provider; + + for (auto it = std::begin(*result); it != std::end(*result); ++it) { + status = GetEncryptionProvider(it->name, provider); + + if (status.ok() && provider) { + size_t prefixLength = provider->GetPrefixLength(); + + if (prefixLength <= it->size_bytes) it->size_bytes -= prefixLength; + } + } + } + + return status; +} + +// Store the size of fname in *file_size. +Status EncryptedEnv2::GetFileSize(const std::string& fname, + uint64_t* file_size) { + Status status; + status = EnvWrapper::GetFileSize(fname, file_size); + + if (status.ok()) { + // this is slightly expensive, but fortunately not used heavily + std::shared_ptr provider; + status = GetEncryptionProvider(fname, provider); + if (status.ok() && provider) { + size_t prefixLength = provider->GetPrefixLength(); + if (prefixLength <= *file_size) *file_size -= prefixLength; + } + } + + return status; +} + +Status EncryptedEnv2::GetEncryptionProvider( + const std::string& fname, std::shared_ptr& provider) { + std::unique_ptr underlying; + EnvOptions options; + Status status; + + provider.reset(); + status = Env::Default()->NewSequentialFile(fname, &underlying, options); + + if (status.ok()) { + std::unique_ptr stream; + status = EncryptedEnv2::ReadSeqEncryptionPrefix(underlying.get(), provider, + stream); + } + + return status; +} + +UnixLibCrypto EncryptedEnv2::crypto_; + +Env* EncryptedEnv2::Default() { + // the rational for this routine is to help force the static + // loading of UnixLibCrypto before other routines start + // using the encryption code. + static EncryptedEnv2 default_env(Env::Default()); + return &default_env; +} + +Env* EncryptedEnv2::Default(EncryptedEnv2::ReadKeys_t encrypt_read, + EncryptedEnv2::WriteKey_t encrypt_write) { + EncryptedEnv2* default_env = (EncryptedEnv2*)Default(); + default_env->SetKeys(encrypt_read, encrypt_write); + return default_env; +} + +#endif // ROCKSDB_LITE + +} // namespace rocksdb + +#endif // ROCKSDB_OPENSSL_AES_CTR diff --git a/env/env_encrypt2_test.cc b/env/env_encrypt2_test.cc new file mode 100644 index 000000000..ca27b4754 --- /dev/null +++ b/env/env_encrypt2_test.cc @@ -0,0 +1,704 @@ +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#include "rocksdb/env_encrypt2.h" + +#include "rocksdb/options.h" +#include "rocksdb/sst_file_writer.h" +#include "util/testharness.h" + +#ifdef ROCKSDB_OPENSSL_AES_CTR + +namespace rocksdb { + +class EnvEncrypt2_Sha1 {}; + +TEST(EnvEncrypt2_Sha1, Default) { + Sha1Description_t desc; + + ASSERT_FALSE(desc.IsValid()); + for (size_t idx = 0; idx < sizeof(desc.desc); ++idx) { + ASSERT_TRUE('\0' == desc.desc[idx]); + } +} + +TEST(EnvEncrypt2_Sha1, Constructors) { + Sha1Description_t desc; + + // verify we know size of desc.desc + ASSERT_TRUE(64 == sizeof(desc.desc)); + + uint8_t bytes[128], *ptr; + for (size_t idx = 0; idx < sizeof(bytes); ++idx) { + bytes[idx] = idx + 1; + } + + Sha1Description_t desc_bad1(bytes, 128); + ASSERT_FALSE(desc_bad1.IsValid()); + + Sha1Description_t desc_bad2(bytes, 65); + ASSERT_FALSE(desc_bad2.IsValid()); + + Sha1Description_t desc_good1(bytes, 64); + ASSERT_TRUE(desc_good1.IsValid()); + ptr = (uint8_t*)memchr(desc_good1.desc, 0, 64); + ASSERT_TRUE(nullptr == ptr); + + Sha1Description_t desc_good2(bytes, 63); + ASSERT_TRUE(desc_good2.IsValid()); + ptr = (uint8_t*)memchr(desc_good2.desc, 0, 64); + ASSERT_TRUE(&desc_good2.desc[63] == ptr); + + Sha1Description_t desc_good3(bytes, 1); + ASSERT_TRUE(desc_good3.IsValid()); + ptr = (uint8_t*)memchr(desc_good3.desc, 0, 64); + ASSERT_TRUE(&desc_good3.desc[1] == ptr); + + Sha1Description_t desc_good4(bytes, 0); + ASSERT_TRUE(desc_good4.IsValid()); + ptr = (uint8_t*)memchr(desc_good4.desc, 0, 64); + ASSERT_TRUE(&desc_good4.desc[0] == ptr); + + Sha1Description_t desc_str1(""); + ASSERT_FALSE(desc_str1.IsValid()); + + uint8_t md2[] = {0x35, 0x6a, 0x19, 0x2b, 0x79, 0x13, 0xb0, 0x4c, 0x54, 0x57, + 0x4d, 0x18, 0xc2, 0x8d, 0x46, 0xe6, 0x39, 0x54, 0x28, 0xab}; + Sha1Description_t desc_str2("1"); + ASSERT_TRUE(desc_str2.IsValid()); + ASSERT_TRUE(0 == memcmp(md2, desc_str2.desc, sizeof(md2))); + for (size_t idx = sizeof(md2); idx < sizeof(desc_str2.desc); ++idx) { + ASSERT_TRUE(0 == desc_str2.desc[idx]); + } + + uint8_t md3[] = {0x7b, 0x52, 0x00, 0x9b, 0x64, 0xfd, 0x0a, 0x2a, 0x49, 0xe6, + 0xd8, 0xa9, 0x39, 0x75, 0x30, 0x77, 0x79, 0x2b, 0x05, 0x54}; + Sha1Description_t desc_str3("12"); + ASSERT_TRUE(desc_str3.IsValid()); + ASSERT_TRUE(0 == memcmp(md3, desc_str3.desc, sizeof(md3))); + for (size_t idx = sizeof(md3); idx < sizeof(desc_str3.desc); ++idx) { + ASSERT_TRUE(0 == desc_str3.desc[idx]); + } +} + +TEST(EnvEncrypt2_Sha1, Copy) { + // assignment + uint8_t md1[] = {0xdb, 0x8a, 0xc1, 0xc2, 0x59, 0xeb, 0x89, 0xd4, 0xa1, 0x31, + 0xb2, 0x53, 0xba, 0xcf, 0xca, 0x5f, 0x31, 0x9d, 0x54, 0xf2}; + Sha1Description_t desc1("HelloWorld"), desc2; + ASSERT_TRUE(desc1.IsValid()); + ASSERT_FALSE(desc2.IsValid()); + + desc2 = desc1; + ASSERT_TRUE(desc1.IsValid()); + ASSERT_TRUE(desc2.IsValid()); + ASSERT_TRUE(0 == memcmp(md1, desc1.desc, sizeof(md1))); + for (size_t idx = sizeof(md1); idx < sizeof(desc1.desc); ++idx) { + ASSERT_TRUE(0 == desc1.desc[idx]); + } + ASSERT_TRUE(0 == memcmp(md1, desc2.desc, sizeof(md1))); + for (size_t idx = sizeof(md1); idx < sizeof(desc2.desc); ++idx) { + ASSERT_TRUE(0 == desc2.desc[idx]); + } + + // copy constructor + uint8_t md3[] = {0x17, 0x09, 0xcc, 0x51, 0x65, 0xf5, 0x50, 0x4d, 0x46, 0xde, + 0x2f, 0x3a, 0x7a, 0xff, 0x57, 0x45, 0x20, 0x8a, 0xed, 0x44}; + Sha1Description_t desc3("A little be longer title for a key"); + ASSERT_TRUE(desc3.IsValid()); + + Sha1Description_t desc4(desc3); + ASSERT_TRUE(desc3.IsValid()); + ASSERT_TRUE(desc4.IsValid()); + ASSERT_TRUE(0 == memcmp(md3, desc3.desc, sizeof(md3))); + for (size_t idx = sizeof(md3); idx < sizeof(desc3.desc); ++idx) { + ASSERT_TRUE(0 == desc3.desc[idx]); + } + ASSERT_TRUE(0 == memcmp(md3, desc4.desc, sizeof(md3))); + for (size_t idx = sizeof(md3); idx < sizeof(desc4.desc); ++idx) { + ASSERT_TRUE(0 == desc4.desc[idx]); + } +} + +class EnvEncrypt2_Key {}; + +TEST(EnvEncrypt2_Key, Default) { + AesCtrKey_t key; + + ASSERT_FALSE(key.IsValid()); + for (size_t idx = 0; idx < sizeof(key.key); ++idx) { + ASSERT_TRUE('\0' == key.key[idx]); + } +} + +TEST(EnvEncrypt2_Key, Constructors) { + AesCtrKey_t key; + + // verify we know size of key.key + ASSERT_TRUE(64 == sizeof(key.key)); + + uint8_t bytes[128], *ptr; + for (size_t idx = 0; idx < sizeof(bytes); ++idx) { + bytes[idx] = idx + 1; + } + + AesCtrKey_t key_bad1(bytes, 128); + ASSERT_FALSE(key_bad1.IsValid()); + + AesCtrKey_t key_bad2(bytes, 65); + ASSERT_FALSE(key_bad2.IsValid()); + + AesCtrKey_t key_good1(bytes, 64); + ASSERT_TRUE(key_good1.IsValid()); + ptr = (uint8_t*)memchr(key_good1.key, 0, 64); + ASSERT_TRUE(nullptr == ptr); + + AesCtrKey_t key_good2(bytes, 63); + ASSERT_TRUE(key_good2.IsValid()); + ptr = (uint8_t*)memchr(key_good2.key, 0, 64); + ASSERT_TRUE(&key_good2.key[63] == ptr); + + AesCtrKey_t key_good3(bytes, 1); + ASSERT_TRUE(key_good3.IsValid()); + ptr = (uint8_t*)memchr(key_good3.key, 0, 64); + ASSERT_TRUE(&key_good3.key[1] == ptr); + + AesCtrKey_t key_good4(bytes, 0); + ASSERT_TRUE(key_good4.IsValid()); + ptr = (uint8_t*)memchr(key_good4.key, 0, 64); + ASSERT_TRUE(&key_good4.key[0] == ptr); + + AesCtrKey_t key_str1(""); + ASSERT_FALSE(key_str1.IsValid()); + + AesCtrKey_t key_str2("0x35"); + ASSERT_FALSE(key_str2.IsValid()); + + // 1234567890123456789012345678901234567890123456789012345678901234 + AesCtrKey_t key_str3( + "RandomSixtyFourCharactersLaLaLaLaJust a bunch of letters, not 0x"); + ASSERT_FALSE(key_str2.IsValid()); + + uint8_t key4[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + // 1234567890123456789012345678901234567890123456789012345678901234 + AesCtrKey_t key_str4( + "0102030405060708090A0B0C0D0E0F101112131415161718191a1b1c1d1e1f20"); + ASSERT_TRUE(key_str4.IsValid()); + ASSERT_TRUE(0 == memcmp(key4, key_str4.key, sizeof(key4))); +} + +TEST(EnvEncrypt2_Key, Copy) { + // assignment + uint8_t data1[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; + AesCtrKey_t key1(data1, sizeof(data1)), key2; + ASSERT_TRUE(key1.IsValid()); + ASSERT_FALSE(key2.IsValid()); + + key2 = key1; + ASSERT_TRUE(key1.IsValid()); + ASSERT_TRUE(key2.IsValid()); + ASSERT_TRUE(0 == memcmp(data1, key1.key, sizeof(data1))); + ASSERT_TRUE(0 == memcmp(data1, key2.key, sizeof(data1))); + + // copy constructor + uint8_t data3[] = {0x21, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0x22, 0x20, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; + AesCtrKey_t key3(data3, sizeof(data3)); + ASSERT_TRUE(key3.IsValid()); + + AesCtrKey_t key4(key3); + ASSERT_TRUE(key3.IsValid()); + ASSERT_TRUE(key4.IsValid()); + ASSERT_TRUE(0 == memcmp(data3, key3.key, sizeof(data3))); + ASSERT_TRUE(0 == memcmp(data3, key4.key, sizeof(data3))); +} + +class EnvEncrypt2_Provider {}; + +class CipherStreamWrapper : public BlockAccessCipherStream { + public: + Status TESTEncryptBlock(uint64_t blockIndex, char* data, char* scratch) { + return EncryptBlock(blockIndex, data, scratch); + } + Status TESTDecryptBlock(uint64_t blockIndex, char* data, char* scratch) { + return DecryptBlock(blockIndex, data, scratch); + } +}; + +TEST(EnvEncrypt2_Provider, NistExamples) { + uint8_t key[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; + uint8_t init[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; + + uint8_t plain1[] = {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a}; + uint8_t cypher1[] = {0x60, 0x1e, 0xc3, 0x13, 0x77, 0x57, 0x89, 0xa5, + 0xb7, 0xa7, 0xf5, 0x04, 0xbb, 0xf3, 0xd2, 0x28}; + + uint8_t plain2[] = {0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51}; + uint8_t cypher2[] = {0xf4, 0x43, 0xe3, 0xca, 0x4d, 0x62, 0xb5, 0x9a, + 0xca, 0x84, 0xe9, 0x90, 0xca, 0xca, 0xf5, 0xc5}; + + uint8_t plain3[] = {0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef}; + uint8_t cypher3[] = {0x2b, 0x09, 0x30, 0xda, 0xa2, 0x3d, 0xe9, 0x4c, + 0xe8, 0x70, 0x17, 0xba, 0x2d, 0x84, 0x98, 0x8d}; + + uint8_t plain4[] = {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10}; + uint8_t cypher4[] = {0xdf, 0xc9, 0xc5, 0x8d, 0xb6, 0x7a, 0xad, 0xa6, + 0x13, 0xc2, 0xdd, 0x08, 0x45, 0x79, 0x41, 0xa6}; + + CTREncryptionProvider2 provider("NistExampleKey", key, sizeof(key)); + // only first 8 bytes of init taken in next call + std::unique_ptr stream( + provider.CreateCipherStream2(1, init)); + + uint64_t offset; + uint8_t block[sizeof(plain1)]; + uint8_t* patch = (uint8_t*)&offset; // little endian assumed + + // + // forward ... encryption + // + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)plain1, 16); + CipherStreamWrapper* wrap = (CipherStreamWrapper*)stream.get(); + + Status status = wrap->TESTEncryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(cypher1, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)plain2, 16); + *(patch + 7) = 0x00; + *(patch + 6) = 0xff; + + status = wrap->TESTEncryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(cypher2, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)plain3, 16); + *(patch + 7) = 0x01; + *(patch + 6) = 0xff; + + status = wrap->TESTEncryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(cypher3, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)plain4, 16); + *(patch + 7) = 0x02; + *(patch + 6) = 0xff; + + status = wrap->TESTEncryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(cypher4, block, sizeof(block))); + + // + // backward -- decryption + // + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)cypher1, 16); + + status = wrap->TESTDecryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(plain1, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)cypher2, 16); + *(patch + 7) = 0x00; + *(patch + 6) = 0xff; + + status = wrap->TESTDecryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(plain2, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)cypher3, 16); + *(patch + 7) = 0x01; + *(patch + 6) = 0xff; + + status = wrap->TESTDecryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(plain3, block, sizeof(block))); + + memcpy((void*)&offset, (void*)&init[8], 8); + memcpy((void*)block, (void*)cypher4, 16); + *(patch + 7) = 0x02; + *(patch + 6) = 0xff; + + status = wrap->TESTDecryptBlock(offset, (char*)block, nullptr); + ASSERT_TRUE(0 == memcmp(plain4, block, sizeof(block))); +} + +// +// The following is copied from env_basic_test.cc +// + +// Normalizes trivial differences across Envs such that these test cases can +// run on all Envs. +class NormalizingEnvWrapper : public EnvWrapper { + public: + explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {} + + // Removes . and .. from directory listing + virtual Status GetChildren(const std::string& dir, + std::vector* result) override { + Status status = EnvWrapper::GetChildren(dir, result); + if (status.ok()) { + result->erase(std::remove_if(result->begin(), result->end(), + [](const std::string& s) { + return s == "." || s == ".."; + }), + result->end()); + } + return status; + } + + // Removes . and .. from directory listing + virtual Status GetChildrenFileAttributes( + const std::string& dir, std::vector* result) override { + Status status = EnvWrapper::GetChildrenFileAttributes(dir, result); + if (status.ok()) { + result->erase(std::remove_if(result->begin(), result->end(), + [](const FileAttributes& fa) { + return fa.name == "." || fa.name == ".."; + }), + result->end()); + } + return status; + } +}; + +class EnvBasicTestWithParam : public testing::Test, + public ::testing::WithParamInterface { + public: + Env* env_; + const EnvOptions soptions_; + std::string test_dir_; + + EnvBasicTestWithParam() : env_(GetParam()) { + test_dir_ = test::PerThreadDBPath(env_, "env_encrypt2_test"); + } + + void SetUp() { env_->CreateDirIfMissing(test_dir_); } + + void TearDown() { + std::vector files; + env_->GetChildren(test_dir_, &files); + for (const auto& file : files) { + // don't know whether it's file or directory, try both. The tests must + // only create files or empty directories, so one must succeed, else the + // directory's corrupted. + Status s = env_->DeleteFile(test_dir_ + "/" + file); + if (!s.ok()) { + ASSERT_OK(env_->DeleteDir(test_dir_ + "/" + file)); + } + } + } +}; + +class EnvMoreTestWithParam : public EnvBasicTestWithParam {}; + +// next statements run env test against encrypt_2 code. +static std::string KeyName = {"A key name"}; +static Sha1Description_t KeyDesc(KeyName); + +// this key is from +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf, +// example F.5.5 +static uint8_t key256[] = {0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4}; +std::shared_ptr encrypt2_provider_ctr( + new CTREncryptionProvider2(KeyName, key256, 32)); + +static EncryptedEnv2::ReadKeys_t encrypt_readers = { + {KeyDesc, encrypt2_provider_ctr}}; +static EncryptedEnv2::WriteKey_t encrypt_writer = {KeyDesc, + encrypt2_provider_ctr}; + +static std::unique_ptr encrypt2_env(new NormalizingEnvWrapper( + EncryptedEnv2::Default(encrypt_readers, encrypt_writer))); + +INSTANTIATE_TEST_CASE_P(EncryptedEnv2, EnvBasicTestWithParam, + ::testing::Values(encrypt2_env.get())); + +TEST_P(EnvBasicTestWithParam, Basics) { + uint64_t file_size; + std::unique_ptr writable_file; + std::vector children; + + // Check that the directory is empty. + ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/non_existent")); + ASSERT_TRUE(!env_->GetFileSize(test_dir_ + "/non_existent", &file_size).ok()); + ASSERT_OK(env_->GetChildren(test_dir_, &children)); + ASSERT_EQ(0U, children.size()); + + // Create a file. + ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + + // Check that the file exists. + ASSERT_OK(env_->FileExists(test_dir_ + "/f")); + ASSERT_OK(env_->GetFileSize(test_dir_ + "/f", &file_size)); + ASSERT_EQ(0U, file_size); + ASSERT_OK(env_->GetChildren(test_dir_, &children)); + ASSERT_EQ(1U, children.size()); + ASSERT_EQ("f", children[0]); + ASSERT_OK(env_->DeleteFile(test_dir_ + "/f")); + + // Write to the file. + ASSERT_OK( + env_->NewWritableFile(test_dir_ + "/f1", &writable_file, soptions_)); + ASSERT_OK(writable_file->Append("abc")); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + ASSERT_OK( + env_->NewWritableFile(test_dir_ + "/f2", &writable_file, soptions_)); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + + // Check for expected size. + ASSERT_OK(env_->GetFileSize(test_dir_ + "/f1", &file_size)); + ASSERT_EQ(3U, file_size); + + // Check that renaming works. + ASSERT_TRUE( + !env_->RenameFile(test_dir_ + "/non_existent", test_dir_ + "/g").ok()); + ASSERT_OK(env_->RenameFile(test_dir_ + "/f1", test_dir_ + "/g")); + ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/f1")); + ASSERT_OK(env_->FileExists(test_dir_ + "/g")); + ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size)); + ASSERT_EQ(3U, file_size); + + // Check that renaming overwriting works + ASSERT_OK(env_->RenameFile(test_dir_ + "/f2", test_dir_ + "/g")); + ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size)); + ASSERT_EQ(0U, file_size); + + // Check that opening non-existent file fails. + std::unique_ptr seq_file; + std::unique_ptr rand_file; + ASSERT_TRUE(!env_->NewSequentialFile(test_dir_ + "/non_existent", &seq_file, + soptions_) + .ok()); + ASSERT_TRUE(!seq_file); + ASSERT_TRUE(!env_->NewRandomAccessFile(test_dir_ + "/non_existent", + &rand_file, soptions_) + .ok()); + ASSERT_TRUE(!rand_file); + + // Check that deleting works. + ASSERT_TRUE(!env_->DeleteFile(test_dir_ + "/non_existent").ok()); + ASSERT_OK(env_->DeleteFile(test_dir_ + "/g")); + ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/g")); + ASSERT_OK(env_->GetChildren(test_dir_, &children)); + ASSERT_EQ(0U, children.size()); + ASSERT_TRUE( + env_->GetChildren(test_dir_ + "/non_existent", &children).IsNotFound()); +} + +TEST_P(EnvBasicTestWithParam, ReadWrite) { + std::unique_ptr writable_file; + std::unique_ptr seq_file; + std::unique_ptr rand_file; + Slice result; + char scratch[100]; + + ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); + ASSERT_OK(writable_file->Append("hello ")); + ASSERT_OK(writable_file->Append("world")); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + + // Read sequentially. + ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_)); + ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello". + ASSERT_EQ(0, result.compare("hello")); + ASSERT_OK(seq_file->Skip(1)); + ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Read "world". + ASSERT_EQ(0, result.compare("world")); + ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Try reading past EOF. + ASSERT_EQ(0U, result.size()); + ASSERT_OK(seq_file->Skip(100)); // Try to skip past end of file. + ASSERT_OK(seq_file->Read(1000, &result, scratch)); + ASSERT_EQ(0U, result.size()); + + // Random reads. + ASSERT_OK(env_->NewRandomAccessFile(test_dir_ + "/f", &rand_file, soptions_)); + ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world". + ASSERT_EQ(0, result.compare("world")); + ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello". + ASSERT_EQ(0, result.compare("hello")); + ASSERT_OK(rand_file->Read(10, 100, &result, scratch)); // Read "d". + ASSERT_EQ(0, result.compare("d")); + + // Too high offset. + ASSERT_TRUE(rand_file->Read(1000, 5, &result, scratch).ok()); +} + +TEST_P(EnvBasicTestWithParam, Misc) { + std::unique_ptr writable_file; + ASSERT_OK(env_->NewWritableFile(test_dir_ + "/b", &writable_file, soptions_)); + + // These are no-ops, but we test they return success. + ASSERT_OK(writable_file->Sync()); + ASSERT_OK(writable_file->Flush()); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); +} + +TEST_P(EnvBasicTestWithParam, LargeWrite) { + const size_t kWriteSize = 300 * 1024; + char* scratch = new char[kWriteSize * 2]; + + std::string write_data; + for (size_t i = 0; i < kWriteSize; ++i) { + write_data.append(1, static_cast(i)); + } + + std::unique_ptr writable_file; + ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_)); + ASSERT_OK(writable_file->Append("foo")); + ASSERT_OK(writable_file->Append(write_data)); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + + std::unique_ptr seq_file; + Slice result; + ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_)); + ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo". + ASSERT_EQ(0, result.compare("foo")); + + size_t read = 0; + std::string read_data; + while (read < kWriteSize) { + ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); + read_data.append(result.data(), result.size()); + read += result.size(); + } + ASSERT_TRUE(write_data == read_data); + delete[] scratch; +} + +TEST_P(EnvMoreTestWithParam, GetModTime) { + ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/dir1")); + uint64_t mtime1 = 0x0; + ASSERT_OK(env_->GetFileModificationTime(test_dir_ + "/dir1", &mtime1)); +} + +TEST_P(EnvMoreTestWithParam, MakeDir) { + ASSERT_OK(env_->CreateDir(test_dir_ + "/j")); + ASSERT_OK(env_->FileExists(test_dir_ + "/j")); + std::vector children; + env_->GetChildren(test_dir_, &children); + ASSERT_EQ(1U, children.size()); + // fail because file already exists + ASSERT_TRUE(!env_->CreateDir(test_dir_ + "/j").ok()); + ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/j")); + ASSERT_OK(env_->DeleteDir(test_dir_ + "/j")); + ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/j")); +} + +TEST_P(EnvMoreTestWithParam, GetChildren) { + // empty folder returns empty vector + std::vector children; + std::vector childAttr; + ASSERT_OK(env_->CreateDirIfMissing(test_dir_)); + ASSERT_OK(env_->GetChildren(test_dir_, &children)); + ASSERT_OK(env_->FileExists(test_dir_)); + ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr)); + ASSERT_EQ(0U, children.size()); + ASSERT_EQ(0U, childAttr.size()); + + // folder with contents returns relative path to test dir + ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/niu")); + ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/you")); + ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/guo")); + ASSERT_OK(env_->GetChildren(test_dir_, &children)); + ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr)); + ASSERT_EQ(3U, children.size()); + ASSERT_EQ(3U, childAttr.size()); + for (auto each : children) { + env_->DeleteDir(test_dir_ + "/" + each); + } // necessary for default POSIX env + + // non-exist directory returns IOError + ASSERT_OK(env_->DeleteDir(test_dir_)); + ASSERT_TRUE(!env_->FileExists(test_dir_).ok()); + ASSERT_TRUE(!env_->GetChildren(test_dir_, &children).ok()); + ASSERT_TRUE(!env_->GetChildrenFileAttributes(test_dir_, &childAttr).ok()); + + // if dir is a file, returns IOError + ASSERT_OK(env_->CreateDir(test_dir_)); + std::unique_ptr writable_file; + ASSERT_OK( + env_->NewWritableFile(test_dir_ + "/file", &writable_file, soptions_)); + ASSERT_OK(writable_file->Close()); + writable_file.reset(); + ASSERT_TRUE(!env_->GetChildren(test_dir_ + "/file", &children).ok()); + ASSERT_EQ(0U, children.size()); +} + +class SstWriterBug : public testing::Test { + public: + std::string test_dir_; + Env* env_default_ = Env::Default(); + + SstWriterBug() { + test_dir_ = test::PerThreadDBPath(env_default_, "env_encrypt2_test"); + } + + void SetUp() { env_default_->CreateDirIfMissing(test_dir_); } + + void TearDown() { + std::vector files; + env_default_->GetChildren(test_dir_, &files); + for (const auto& file : files) { + // don't know whether it's file or directory, try both. The tests must + // only create files or empty directories, so one must succeed, else the + // directory's corrupted. + Status s = env_default_->DeleteFile(test_dir_ + "/" + file); + if (!s.ok()) { + ASSERT_OK(env_default_->DeleteDir(test_dir_ + "/" + file)); + } + } + } +}; + +#if 0 +TEST(SstWriterBug, BugCheck) { + + Options sstOptions; + + sstOptions.env = encrypt2_env.get(); + + // auto* cf = reinterpret_cast(theCfHandle); + rocksdb::ColumnFamilyHandle * cf = nullptr; + // sstOptions.compression = (CompressionType)theCompression; + auto* sst_file_writer = new rocksdb::SstFileWriter(EnvOptions(), sstOptions, sstOptions.comparator, cf); + std::string path = test::PerThreadDBPath("BugCheck1"); + Status ss = sst_file_writer->Open(path); + ASSERT_OK(ss); +} +#endif + +} // namespace rocksdb + +#endif // ROCKSDB_OPENSSL_AES_CTR + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/env/env_encryption.cc b/env/env_encryption.cc index e38693e3c..dfbcccaf4 100644 --- a/env/env_encryption.cc +++ b/env/env_encryption.cc @@ -5,11 +5,14 @@ #ifndef ROCKSDB_LITE +#include "rocksdb/env_encryption.h" + #include +#include #include #include -#include "rocksdb/env_encryption.h" +#include "monitoring/perf_context_imp.h" #include "util/aligned_buffer.h" #include "util/coding.h" #include "util/random.h" @@ -20,19 +23,6 @@ namespace rocksdb { #ifndef ROCKSDB_LITE -class EncryptedSequentialFile : public SequentialFile { - private: - std::unique_ptr file_; - std::unique_ptr stream_; - uint64_t offset_; - size_t prefixLength_; - - public: - // Default ctor. Given underlying sequential file is supposed to be at - // offset == prefixLength. - EncryptedSequentialFile(SequentialFile* f, BlockAccessCipherStream* s, size_t prefixLength) - : file_(f), stream_(s), offset_(prefixLength), prefixLength_(prefixLength) { - } // Read up to "n" bytes from the file. "scratch[0..n-1]" may be // written by this routine. Sets "*result" to the data that was @@ -42,78 +32,73 @@ class EncryptedSequentialFile : public SequentialFile { // If an error was encountered, returns a non-OK status. // // REQUIRES: External synchronization - virtual Status Read(size_t n, Slice* result, char* scratch) override { - assert(scratch); - Status status = file_->Read(n, result, scratch); - if (!status.ok()) { - return status; - } - status = stream_->Decrypt(offset_, (char*)result->data(), result->size()); - offset_ += result->size(); // We've already ready data from disk, so update offset_ even if decryption fails. +Status EncryptedSequentialFile::Read(size_t n, Slice* result, char* scratch) { + assert(scratch); + Status status = file_->Read(n, result, scratch); + if (!status.ok()) { return status; } + { + PERF_TIMER_GUARD(decrypt_data_nanos); + status = stream_->Decrypt(offset_, (char*)result->data(), result->size()); + } + offset_ += result->size(); // We've already ready data from disk, so update + // offset_ even if decryption fails. + return status; +} - // Skip "n" bytes from the file. This is guaranteed to be no - // slower that reading the same data, but may be faster. - // - // If end of file is reached, skipping will stop at the end of the - // file, and Skip will return OK. - // - // REQUIRES: External synchronization - virtual Status Skip(uint64_t n) override { - auto status = file_->Skip(n); - if (!status.ok()) { - return status; - } - offset_ += n; +// Skip "n" bytes from the file. This is guaranteed to be no +// slower that reading the same data, but may be faster. +// +// If end of file is reached, skipping will stop at the end of the +// file, and Skip will return OK. +// +// REQUIRES: External synchronization +Status EncryptedSequentialFile::Skip(uint64_t n) { + auto status = file_->Skip(n); + if (!status.ok()) { return status; } + offset_ += n; + return status; +} - // Indicates the upper layers if the current SequentialFile implementation - // uses direct IO. - virtual bool use_direct_io() const override { - return file_->use_direct_io(); - } +// Indicates the upper layers if the current SequentialFile implementation +// uses direct IO. +bool EncryptedSequentialFile::use_direct_io() const { + return file_->use_direct_io(); +} - // Use the returned alignment value to allocate - // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); - } +// Use the returned alignment value to allocate +// aligned buffer for Direct I/O +size_t EncryptedSequentialFile::GetRequiredBufferAlignment() const { + return file_->GetRequiredBufferAlignment(); +} // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) override { - return file_->InvalidateCache(offset + prefixLength_, length); - } +Status EncryptedSequentialFile::InvalidateCache(size_t offset, size_t length) { + return file_->InvalidateCache(offset + prefixLength_, length); +} // Positioned Read for direct I/O // If Direct I/O enabled, offset, n, and scratch should be properly aligned - virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result, char* scratch) override { - assert(scratch); - offset += prefixLength_; // Skip prefix - auto status = file_->PositionedRead(offset, n, result, scratch); - if (!status.ok()) { - return status; - } - offset_ = offset + result->size(); - status = stream_->Decrypt(offset, (char*)result->data(), result->size()); +Status EncryptedSequentialFile::PositionedRead(uint64_t offset, size_t n, + Slice* result, char* scratch) { + assert(scratch); + offset += prefixLength_; // Skip prefix + auto status = file_->PositionedRead(offset, n, result, scratch); + if (!status.ok()) { return status; } - -}; - -// A file abstraction for randomly reading the contents of a file. -class EncryptedRandomAccessFile : public RandomAccessFile { - private: - std::unique_ptr file_; - std::unique_ptr stream_; - size_t prefixLength_; - - public: - EncryptedRandomAccessFile(RandomAccessFile* f, BlockAccessCipherStream* s, size_t prefixLength) - : file_(f), stream_(s), prefixLength_(prefixLength) { } + offset_ = offset + result->size(); + { + PERF_TIMER_GUARD(decrypt_data_nanos); + status = stream_->Decrypt(offset, (char*)result->data(), result->size()); + } + return status; +} // Read up to "n" bytes from the file starting at "offset". // "scratch[0..n-1]" may be written by this routine. Sets "*result" @@ -125,22 +110,26 @@ class EncryptedRandomAccessFile : public RandomAccessFile { // // Safe for concurrent use by multiple threads. // If Direct I/O enabled, offset, n, and scratch should be aligned properly. - virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { - assert(scratch); - offset += prefixLength_; - auto status = file_->Read(offset, n, result, scratch); - if (!status.ok()) { - return status; - } - status = stream_->Decrypt(offset, (char*)result->data(), result->size()); +Status EncryptedRandomAccessFile::Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const { + assert(scratch); + offset += prefixLength_; + auto status = file_->Read(offset, n, result, scratch); + if (!status.ok()) { return status; } + { + PERF_TIMER_GUARD(decrypt_data_nanos); + status = stream_->Decrypt(offset, (char*)result->data(), result->size()); + } + return status; +} // Readahead the file starting from offset by n bytes for caching. - virtual Status Prefetch(uint64_t offset, size_t n) override { - //return Status::OK(); - return file_->Prefetch(offset + prefixLength_, n); - } +Status EncryptedRandomAccessFile::Prefetch(uint64_t offset, size_t n) { + // return Status::OK(); + return file_->Prefetch(offset + prefixLength_, n); +} // Tries to get an unique ID for this file that will be the same each time // the file is opened (and will stay the same while the file is open). @@ -157,124 +146,128 @@ class EncryptedRandomAccessFile : public RandomAccessFile { // a single varint. // // Note: these IDs are only valid for the duration of the process. - virtual size_t GetUniqueId(char* id, size_t max_size) const override { - return file_->GetUniqueId(id, max_size); - }; +size_t EncryptedRandomAccessFile::GetUniqueId(char* id, size_t max_size) const { + return file_->GetUniqueId(id, max_size); +}; - virtual void Hint(AccessPattern pattern) override { - file_->Hint(pattern); - } +void EncryptedRandomAccessFile::Hint(AccessPattern pattern) { + file_->Hint(pattern); +} // Indicates the upper layers if the current RandomAccessFile implementation // uses direct IO. - virtual bool use_direct_io() const override { - return file_->use_direct_io(); - } +bool EncryptedRandomAccessFile::use_direct_io() const { + return file_->use_direct_io(); +} // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); - } +size_t EncryptedRandomAccessFile::GetRequiredBufferAlignment() const { + return file_->GetRequiredBufferAlignment(); +} // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. - virtual Status InvalidateCache(size_t offset, size_t length) override { - return file_->InvalidateCache(offset + prefixLength_, length); - } -}; +Status EncryptedRandomAccessFile::InvalidateCache(size_t offset, + size_t length) { + return file_->InvalidateCache(offset + prefixLength_, length); +} // A file abstraction for sequential writing. The implementation // must provide buffering since callers may append small fragments // at a time to the file. -class EncryptedWritableFile : public WritableFileWrapper { - private: - std::unique_ptr file_; - std::unique_ptr stream_; - size_t prefixLength_; - - public: - // Default ctor. Prefix is assumed to be written already. - EncryptedWritableFile(WritableFile* f, BlockAccessCipherStream* s, size_t prefixLength) - : WritableFileWrapper(f), file_(f), stream_(s), prefixLength_(prefixLength) { } - - Status Append(const Slice& data) override { - AlignedBuffer buf; - Status status; - Slice dataToAppend(data); - if (data.size() > 0) { - auto offset = file_->GetFileSize(); // size including prefix - // Encrypt in cloned buffer - buf.Alignment(GetRequiredBufferAlignment()); - buf.AllocateNewBuffer(data.size()); - memmove(buf.BufferStart(), data.data(), data.size()); - status = stream_->Encrypt(offset, buf.BufferStart(), data.size()); - if (!status.ok()) { - return status; - } - dataToAppend = Slice(buf.BufferStart(), data.size()); +Status EncryptedWritableFile::Append(const Slice& data) { + AlignedBuffer buf; + Status status; + Slice dataToAppend(data); + if (data.size() > 0) { + auto offset = file_->GetFileSize(); // size including prefix + // Encrypt in cloned buffer + buf.Alignment(GetRequiredBufferAlignment()); + buf.AllocateNewBuffer(data.size()); + // TODO (sagar0): Modify AlignedBuffer.Append to allow doing a memmove + // so that the next two lines can be replaced with buf.Append(). + memmove(buf.BufferStart(), data.data(), data.size()); + buf.Size(data.size()); + { + PERF_TIMER_GUARD(encrypt_data_nanos); + status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize()); } - status = file_->Append(dataToAppend); if (!status.ok()) { return status; } + dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize()); + } + status = file_->Append(dataToAppend); + if (!status.ok()) { return status; } + return status; +} - Status PositionedAppend(const Slice& data, uint64_t offset) override { - AlignedBuffer buf; - Status status; - Slice dataToAppend(data); - offset += prefixLength_; - if (data.size() > 0) { - // Encrypt in cloned buffer - buf.Alignment(GetRequiredBufferAlignment()); - buf.AllocateNewBuffer(data.size()); - memmove(buf.BufferStart(), data.data(), data.size()); - status = stream_->Encrypt(offset, buf.BufferStart(), data.size()); - if (!status.ok()) { - return status; - } - dataToAppend = Slice(buf.BufferStart(), data.size()); +Status EncryptedWritableFile::PositionedAppend(const Slice& data, + uint64_t offset) { + AlignedBuffer buf; + Status status; + Slice dataToAppend(data); + offset += prefixLength_; + if (data.size() > 0) { + // Encrypt in cloned buffer + buf.Alignment(GetRequiredBufferAlignment()); + buf.AllocateNewBuffer(data.size()); + memmove(buf.BufferStart(), data.data(), data.size()); + buf.Size(data.size()); + { + PERF_TIMER_GUARD(encrypt_data_nanos); + status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize()); } - status = file_->PositionedAppend(dataToAppend, offset); if (!status.ok()) { return status; } + dataToAppend = Slice(buf.BufferStart(), buf.CurrentSize()); + } + status = file_->PositionedAppend(dataToAppend, offset); + if (!status.ok()) { return status; } + return status; +} // Indicates the upper layers if the current WritableFile implementation // uses direct IO. - virtual bool use_direct_io() const override { return file_->use_direct_io(); } +bool EncryptedWritableFile::use_direct_io() const { + return file_->use_direct_io(); +} // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { return file_->GetRequiredBufferAlignment(); } +size_t EncryptedWritableFile::GetRequiredBufferAlignment() const { + return file_->GetRequiredBufferAlignment(); +} - /* - * Get the size of valid data in the file. - */ - virtual uint64_t GetFileSize() override { - return file_->GetFileSize() - prefixLength_; - } +/* + * Get the size of valid data in the file. + */ +uint64_t EncryptedWritableFile::GetFileSize() { + return file_->GetFileSize() - prefixLength_; +} // Truncate is necessary to trim the file to the correct size // before closing. It is not always possible to keep track of the file // size due to whole pages writes. The behavior is undefined if called // with other writes to follow. - virtual Status Truncate(uint64_t size) override { - return file_->Truncate(size + prefixLength_); - } +Status EncryptedWritableFile::Truncate(uint64_t size) { + return file_->Truncate(size + prefixLength_); +} // Remove any kind of caching of data from the offset to offset+length // of this file. If the length is 0, then it refers to the end of file. // If the system is not caching the file contents, then this is a noop. // This call has no effect on dirty pages in the cache. - virtual Status InvalidateCache(size_t offset, size_t length) override { - return file_->InvalidateCache(offset + prefixLength_, length); - } +Status EncryptedWritableFile::InvalidateCache(size_t offset, size_t length) { + return file_->InvalidateCache(offset + prefixLength_, length); +} // Sync a file range with disk. // offset is the starting byte of the file range to be synchronized. @@ -282,103 +275,95 @@ class EncryptedWritableFile : public WritableFileWrapper { // This asks the OS to initiate flushing the cached data to disk, // without waiting for completion. // Default implementation does nothing. - virtual Status RangeSync(uint64_t offset, uint64_t nbytes) override { - return file_->RangeSync(offset + prefixLength_, nbytes); - } +Status EncryptedWritableFile::RangeSync(uint64_t offset, uint64_t nbytes) { + return file_->RangeSync(offset + prefixLength_, nbytes); +} // PrepareWrite performs any necessary preparation for a write // before the write actually occurs. This allows for pre-allocation // of space on devices where it can result in less file // fragmentation and/or less waste from over-zealous filesystem // pre-allocation. - virtual void PrepareWrite(size_t offset, size_t len) override { - file_->PrepareWrite(offset + prefixLength_, len); - } +void EncryptedWritableFile::PrepareWrite(size_t offset, size_t len) { + file_->PrepareWrite(offset + prefixLength_, len); +} // Pre-allocates space for a file. - virtual Status Allocate(uint64_t offset, uint64_t len) override { - return file_->Allocate(offset + prefixLength_, len); - } -}; +Status EncryptedWritableFile::Allocate(uint64_t offset, uint64_t len) { + return file_->Allocate(offset + prefixLength_, len); +} // A file abstraction for random reading and writing. -class EncryptedRandomRWFile : public RandomRWFile { - private: - std::unique_ptr file_; - std::unique_ptr stream_; - size_t prefixLength_; - public: - EncryptedRandomRWFile(RandomRWFile* f, BlockAccessCipherStream* s, size_t prefixLength) - : file_(f), stream_(s), prefixLength_(prefixLength) {} - - // Indicates if the class makes use of direct I/O - // If false you must pass aligned buffer to Write() - virtual bool use_direct_io() const override { return file_->use_direct_io(); } +// Indicates if the class makes use of direct I/O +// If false you must pass aligned buffer to Write() +bool EncryptedRandomRWFile::use_direct_io() const { + return file_->use_direct_io(); +} // Use the returned alignment value to allocate // aligned buffer for Direct I/O - virtual size_t GetRequiredBufferAlignment() const override { - return file_->GetRequiredBufferAlignment(); - } +size_t EncryptedRandomRWFile::GetRequiredBufferAlignment() const { + return file_->GetRequiredBufferAlignment(); +} // Write bytes in `data` at offset `offset`, Returns Status::OK() on success. // Pass aligned buffer when use_direct_io() returns true. - virtual Status Write(uint64_t offset, const Slice& data) override { - AlignedBuffer buf; - Status status; - Slice dataToWrite(data); - offset += prefixLength_; - if (data.size() > 0) { - // Encrypt in cloned buffer - buf.Alignment(GetRequiredBufferAlignment()); - buf.AllocateNewBuffer(data.size()); - memmove(buf.BufferStart(), data.data(), data.size()); - status = stream_->Encrypt(offset, buf.BufferStart(), data.size()); - if (!status.ok()) { - return status; - } - dataToWrite = Slice(buf.BufferStart(), data.size()); +Status EncryptedRandomRWFile::Write(uint64_t offset, const Slice& data) { + AlignedBuffer buf; + Status status; + Slice dataToWrite(data); + offset += prefixLength_; + if (data.size() > 0) { + // Encrypt in cloned buffer + buf.Alignment(GetRequiredBufferAlignment()); + buf.AllocateNewBuffer(data.size()); + memmove(buf.BufferStart(), data.data(), data.size()); + buf.Size(data.size()); + { + PERF_TIMER_GUARD(encrypt_data_nanos); + status = stream_->Encrypt(offset, buf.BufferStart(), buf.CurrentSize()); } - status = file_->Write(offset, dataToWrite); - return status; + if (!status.ok()) { + return status; + } + dataToWrite = Slice(buf.BufferStart(), buf.CurrentSize()); } + status = file_->Write(offset, dataToWrite); + return status; +} // Read up to `n` bytes starting from offset `offset` and store them in // result, provided `scratch` size should be at least `n`. // Returns Status::OK() on success. - virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { - assert(scratch); - offset += prefixLength_; - auto status = file_->Read(offset, n, result, scratch); - if (!status.ok()) { - return status; - } - status = stream_->Decrypt(offset, (char*)result->data(), result->size()); +Status EncryptedRandomRWFile::Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const { + assert(scratch); + offset += prefixLength_; + auto status = file_->Read(offset, n, result, scratch); + if (!status.ok()) { return status; } - - virtual Status Flush() override { - return file_->Flush(); + { + PERF_TIMER_GUARD(decrypt_data_nanos); + status = stream_->Decrypt(offset, (char*)result->data(), result->size()); } + return status; +} - virtual Status Sync() override { - return file_->Sync(); - } +Status EncryptedRandomRWFile::Flush() { return file_->Flush(); } - virtual Status Fsync() override { - return file_->Fsync(); - } +Status EncryptedRandomRWFile::Sync() { return file_->Sync(); } - virtual Status Close() override { - return file_->Close(); - } -}; +Status EncryptedRandomRWFile::Fsync() { return file_->Fsync(); } + +Status EncryptedRandomRWFile::Close() { return file_->Close(); } -// EncryptedEnv implements an Env wrapper that adds encryption to files stored on disk. +// EncryptedEnv implements an Env wrapper that adds encryption to files stored +// on disk. class EncryptedEnv : public EnvWrapper { public: - EncryptedEnv(Env* base_env, EncryptionProvider *provider) + EncryptedEnv(Env* base_env, EncryptionProvider* provider) : EnvWrapper(base_env) { provider_ = provider; } @@ -402,21 +387,25 @@ class EncryptedEnv : public EnvWrapper { Slice prefixSlice; size_t prefixLength = provider_->GetPrefixLength(); if (prefixLength > 0) { - // Read prefix + // Read prefix prefixBuf.Alignment(underlying->GetRequiredBufferAlignment()); prefixBuf.AllocateNewBuffer(prefixLength); - status = underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart()); + status = + underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart()); if (!status.ok()) { return status; } + prefixBuf.Size(prefixLength); } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedSequentialFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedSequentialFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } @@ -439,21 +428,25 @@ class EncryptedEnv : public EnvWrapper { Slice prefixSlice; size_t prefixLength = provider_->GetPrefixLength(); if (prefixLength > 0) { - // Read prefix + // Read prefix prefixBuf.Alignment(underlying->GetRequiredBufferAlignment()); prefixBuf.AllocateNewBuffer(prefixLength); - status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart()); + status = underlying->Read(0, prefixLength, &prefixSlice, + prefixBuf.BufferStart()); if (!status.ok()) { return status; } + prefixBuf.Size(prefixLength); } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedRandomAccessFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedRandomAccessFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } @@ -476,12 +469,13 @@ class EncryptedEnv : public EnvWrapper { Slice prefixSlice; size_t prefixLength = provider_->GetPrefixLength(); if (prefixLength > 0) { - // Initialize prefix + // Initialize prefix prefixBuf.Alignment(underlying->GetRequiredBufferAlignment()); prefixBuf.AllocateNewBuffer(prefixLength); provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength); - prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength); - // Write prefix + prefixBuf.Size(prefixLength); + prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize()); + // Write prefix status = underlying->Append(prefixSlice); if (!status.ok()) { return status; @@ -489,11 +483,13 @@ class EncryptedEnv : public EnvWrapper { } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } @@ -522,12 +518,13 @@ class EncryptedEnv : public EnvWrapper { Slice prefixSlice; size_t prefixLength = provider_->GetPrefixLength(); if (prefixLength > 0) { - // Initialize prefix + // Initialize prefix prefixBuf.Alignment(underlying->GetRequiredBufferAlignment()); prefixBuf.AllocateNewBuffer(prefixLength); provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength); - prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength); - // Write prefix + prefixBuf.Size(prefixLength); + prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize()); + // Write prefix status = underlying->Append(prefixSlice); if (!status.ok()) { return status; @@ -535,11 +532,13 @@ class EncryptedEnv : public EnvWrapper { } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } @@ -554,7 +553,8 @@ class EncryptedEnv : public EnvWrapper { } // Open file using underlying Env implementation std::unique_ptr underlying; - Status status = EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options); + Status status = + EnvWrapper::ReuseWritableFile(fname, old_fname, &underlying, options); if (!status.ok()) { return status; } @@ -563,12 +563,13 @@ class EncryptedEnv : public EnvWrapper { Slice prefixSlice; size_t prefixLength = provider_->GetPrefixLength(); if (prefixLength > 0) { - // Initialize prefix + // Initialize prefix prefixBuf.Alignment(underlying->GetRequiredBufferAlignment()); prefixBuf.AllocateNewBuffer(prefixLength); provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength); - prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength); - // Write prefix + prefixBuf.Size(prefixLength); + prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize()); + // Write prefix status = underlying->Append(prefixSlice); if (!status.ok()) { return status; @@ -576,11 +577,13 @@ class EncryptedEnv : public EnvWrapper { } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedWritableFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedWritableFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } @@ -614,15 +617,19 @@ class EncryptedEnv : public EnvWrapper { prefixBuf.AllocateNewBuffer(prefixLength); if (!isNewFile) { // File already exists, read prefix - status = underlying->Read(0, prefixLength, &prefixSlice, prefixBuf.BufferStart()); + status = underlying->Read(0, prefixLength, &prefixSlice, + prefixBuf.BufferStart()); if (!status.ok()) { return status; } + prefixBuf.Size(prefixLength); } else { - // File is new, initialize & write prefix - provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength); - prefixSlice = Slice(prefixBuf.BufferStart(), prefixLength); - // Write prefix + // File is new, initialize & write prefix + provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), + prefixLength); + prefixBuf.Size(prefixLength); + prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize()); + // Write prefix status = underlying->Write(0, prefixSlice); if (!status.ok()) { return status; @@ -631,39 +638,51 @@ class EncryptedEnv : public EnvWrapper { } // Create cipher stream std::unique_ptr stream; - status = provider_->CreateCipherStream(fname, options, prefixSlice, &stream); + status = + provider_->CreateCipherStream(fname, options, prefixSlice, &stream); if (!status.ok()) { return status; } - (*result) = std::unique_ptr(new EncryptedRandomRWFile(underlying.release(), stream.release(), prefixLength)); + (*result) = std::unique_ptr(new EncryptedRandomRWFile( + underlying.release(), stream.release(), prefixLength)); return Status::OK(); } - // Store in *result the attributes of the children of the specified directory. - // In case the implementation lists the directory prior to iterating the files - // and files are concurrently deleted, the deleted files will be omitted from + // Store in *result the attributes of the children of the specified + // directory. + // In case the implementation lists the directory prior to iterating the + // files + // and files are concurrently deleted, the deleted files will be omitted + // from // result. // The name attributes are relative to "dir". // Original contents of *results are dropped. // Returns OK if "dir" exists and "*result" contains its children. - // NotFound if "dir" does not exist, the calling process does not have + // NotFound if "dir" does not exist, the calling process does not + // have // permission to access "dir", or if "dir" is invalid. // IOError if an IO Error was encountered - virtual Status GetChildrenFileAttributes(const std::string& dir, std::vector* result) override { + virtual Status GetChildrenFileAttributes( + const std::string& dir, std::vector* result) override { auto status = EnvWrapper::GetChildrenFileAttributes(dir, result); if (!status.ok()) { return status; } size_t prefixLength = provider_->GetPrefixLength(); - for (auto it = std::begin(*result); it!=std::end(*result); ++it) { - assert(it->size_bytes >= prefixLength); + for (auto it = std::begin(*result); it != std::end(*result); ++it) { + // assert(it->size_bytes >= prefixLength); + // breaks env_basic_test when called on directory containing + // directories + // which makes subtraction of prefixLength worrisome since + // FileAttributes does not identify directories it->size_bytes -= prefixLength; } return Status::OK(); - } + } // Store the size of fname in *file_size. - virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) override { + virtual Status GetFileSize(const std::string& fname, + uint64_t* file_size) override { auto status = EnvWrapper::GetFileSize(fname, file_size); if (!status.ok()) { return status; @@ -671,15 +690,14 @@ class EncryptedEnv : public EnvWrapper { size_t prefixLength = provider_->GetPrefixLength(); assert(*file_size >= prefixLength); *file_size -= prefixLength; - return Status::OK(); + return Status::OK(); } private: - EncryptionProvider *provider_; + EncryptionProvider* provider_; }; - -// Returns an Env that encrypts data when stored on disk and decrypts data when +// Returns an Env that encrypts data when stored on disk and decrypts data when // read from disk. Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider) { return new EncryptedEnv(base_env, provider); @@ -702,14 +720,14 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char *data, size_t char *block = data; size_t n = std::min(dataSize, blockSize - blockOffset); if (n != blockSize) { - // We're not encrypting a full block. + // We're not encrypting a full block. // Copy data to blockBuffer if (!blockBuffer.get()) { // Allocate buffer blockBuffer = std::unique_ptr(new char[blockSize]); } block = blockBuffer.get(); - // Copy plain data to block buffer + // Copy plain data to block buffer memmove(block + blockOffset, data, n); } auto status = EncryptBlock(blockIndex, block, (char*)scratch.data()); @@ -747,14 +765,14 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t char *block = data; size_t n = std::min(dataSize, blockSize - blockOffset); if (n != blockSize) { - // We're not decrypting a full block. + // We're not decrypting a full block. // Copy data to blockBuffer if (!blockBuffer.get()) { // Allocate buffer blockBuffer = std::unique_ptr(new char[blockSize]); } block = blockBuffer.get(); - // Copy encrypted data to block buffer + // Copy encrypted data to block buffer memmove(block + blockOffset, data, n); } auto status = DecryptBlock(blockIndex, block, (char*)scratch.data()); @@ -765,6 +783,14 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t // Copy decrypted data back to `data`. memmove(data, block + blockOffset, n); } + + // Simply decrementing dataSize by n could cause it to underflow, + // which will very likely make it read over the original bounds later + assert(dataSize >= n); + if (dataSize < n) { + return Status::Corruption("Cannot decrypt data at given offset"); + } + dataSize -= n; if (dataSize == 0) { return Status::OK(); @@ -805,7 +831,7 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scra memmove(scratch, iv_.data(), blockSize); EncodeFixed64(scratch, blockIndex + initialCounter_); - // Encrypt nonce+counter + // Encrypt nonce+counter auto status = cipher_.Encrypt(scratch); if (!status.ok()) { return status; @@ -821,13 +847,13 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scra // Decrypt a block of data at the given block index. // Length of data is equal to BlockSize(); Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char *data, char* scratch) { - // For CTR decryption & encryption are the same + // For CTR decryption & encryption are the same return EncryptBlock(blockIndex, data, scratch); } // GetPrefixLength returns the length of the prefix that is added to every file // and used for storing encryption options. -// For optimal performance, the prefix length should be a multiple of +// For optimal performance, the prefix length should be a multiple of // the page size. size_t CTREncryptionProvider::GetPrefixLength() { return defaultPrefixLength; @@ -842,7 +868,7 @@ static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t & iv = Slice(prefix + blockSize, blockSize); } -// CreateNewPrefix initialized an allocated block of prefix memory +// CreateNewPrefix initialized an allocated block of prefix memory // for a new file. Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/, char* prefix, @@ -862,16 +888,22 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/, // Now populate the rest of the prefix, starting from the third block. PopulateSecretPrefixPart(prefix + (2 * blockSize), prefixLength - (2 * blockSize), blockSize); - // Encrypt the prefix, starting from block 2 (leave block 0, 1 with initial counter & IV unencrypted) + // Encrypt the prefix, starting from block 2 (leave block 0, 1 with initial + // counter & IV unencrypted) CTRCipherStream cipherStream(cipher_, prefixIV.data(), initialCounter); - auto status = cipherStream.Encrypt(0, prefix + (2 * blockSize), prefixLength - (2 * blockSize)); + Status status; + { + PERF_TIMER_GUARD(encrypt_data_nanos); + status = cipherStream.Encrypt(0, prefix + (2 * blockSize), + prefixLength - (2 * blockSize)); + } if (!status.ok()) { return status; } return Status::OK(); } -// PopulateSecretPrefixPart initializes the data into a new prefix block +// PopulateSecretPrefixPart initializes the data into a new prefix block // in plain text. // Returns the amount of space (starting from the start of the prefix) // that has been initialized. @@ -891,14 +923,28 @@ Status CTREncryptionProvider::CreateCipherStream( Slice iv; decodeCTRParameters(prefix.data(), blockSize, initialCounter, iv); - // Decrypt the encrypted part of the prefix, starting from block 2 (block 0, 1 with initial counter & IV are unencrypted) + // If the prefix is smaller than twice the block size, we would below read a + // very large chunk of the file (and very likely read over the bounds) + assert(prefix.size() >= 2 * blockSize); + if (prefix.size() < 2 * blockSize) { + return Status::Corruption("Unable to read from file " + fname + + ": read attempt would read beyond file bounds"); + } + + // Decrypt the encrypted part of the prefix, starting from block 2 (block 0, 1 + // with initial counter & IV are unencrypted) CTRCipherStream cipherStream(cipher_, iv.data(), initialCounter); - auto status = cipherStream.Decrypt(0, (char*)prefix.data() + (2 * blockSize), prefix.size() - (2 * blockSize)); + Status status; + { + PERF_TIMER_GUARD(decrypt_data_nanos); + status = cipherStream.Decrypt(0, (char*)prefix.data() + (2 * blockSize), + prefix.size() - (2 * blockSize)); + } if (!status.ok()) { return status; } - // Create cipher stream + // Create cipher stream return CreateCipherStreamFromPrefix(fname, options, initialCounter, iv, prefix, result); } diff --git a/include/BUILD b/include/BUILD index ec9fc4a6b..a95c20ba3 100644 --- a/include/BUILD +++ b/include/BUILD @@ -1,12 +1,24 @@ +DEPS = select({ + "@toolchain//:is_target_linux": [ + ], + "@toolchain//:is_target_osx": [ + "@openssl_osx//:openssl_dev", + ], + "@toolchain//:is_target_windows": [ + ], +}) + cc_library( name = "include", hdrs = glob(["**/*.h"]), visibility = ["//visibility:public"], + deps = DEPS, strip_include_prefix = "/include", ) cc_library( name = "with_extra_prefix", hdrs = glob(["**/*.h"]), + deps = DEPS, visibility = ["//visibility:public"], ) diff --git a/include/rocksdb/env_encrypt2.h b/include/rocksdb/env_encrypt2.h new file mode 100644 index 000000000..dc2669d94 --- /dev/null +++ b/include/rocksdb/env_encrypt2.h @@ -0,0 +1,469 @@ +// copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +// +// env_encryption.cc copied to this file then modified. + +#ifdef ROCKSDB_OPENSSL_AES_CTR +#ifndef ROCKSDB_LITE + +#include +#include +#include + +#include "openssl/aes.h" +#include "openssl/evp.h" +#include "openssl/rand.h" +#include "rocksdb/env_encryption.h" +#include "util/aligned_buffer.h" +#include "util/coding.h" +#include "util/library_loader.h" +#include "util/random.h" + +#endif + +namespace rocksdb { + +#ifndef ROCKSDB_LITE + +struct Sha1Description_t { + uint8_t desc[EVP_MAX_MD_SIZE]; + bool valid; + + Sha1Description_t() : valid(false) { memset(desc, 0, EVP_MAX_MD_SIZE); } + + Sha1Description_t(const Sha1Description_t& rhs) { *this = rhs; } + + Sha1Description_t& operator=(const Sha1Description_t& rhs) { + memcpy(desc, rhs.desc, sizeof(desc)); + valid = rhs.valid; + return *this; + } + + Sha1Description_t(uint8_t* Desc, size_t DescLen) : valid(false) { + memset(desc, 0, EVP_MAX_MD_SIZE); + if (DescLen <= EVP_MAX_MD_SIZE) { + memcpy(desc, Desc, DescLen); + valid = true; + } + } + + Sha1Description_t(const std::string& key_desc_str); + + // see AesCtrKey_t destructor below. This data is not really + // essential to clear, but trying to set pattern for future work. + // goal is to explicitly remove desc from memory once no longer needed + ~Sha1Description_t() { + memset(desc, 0, EVP_MAX_MD_SIZE); + valid = false; + } + + bool operator<(const Sha1Description_t& rhs) const { + return memcmp(desc, rhs.desc, EVP_MAX_MD_SIZE) < 0; + } + + bool operator==(const Sha1Description_t& rhs) const { + return 0 == memcmp(desc, rhs.desc, EVP_MAX_MD_SIZE) && valid == rhs.valid; + } + + bool IsValid() const { return valid; } +}; + +struct AesCtrKey_t { + uint8_t key[EVP_MAX_KEY_LENGTH]; + bool valid; + + AesCtrKey_t() : valid(false) { memset(key, 0, EVP_MAX_KEY_LENGTH); } + + AesCtrKey_t(const uint8_t* Key, size_t KeyLen) : valid(false) { + memset(key, 0, EVP_MAX_KEY_LENGTH); + if (KeyLen <= EVP_MAX_KEY_LENGTH) { + memcpy(key, Key, KeyLen); + valid = true; + } else { + valid = false; + } + } + + AesCtrKey_t(const std::string& key_str); + + // see Writing Solid Code, 2nd edition + // Chapter 9, page 321, Managing Secrets in Memory ... bullet 4 "Scrub the memory" + // Not saying this is essential or effective in initial implementation since current + // usage model loads all keys at start and only deletes them at shutdown. But does + // establish presidence. + // goal is to explicitly remove key from memory once no longer needed + ~AesCtrKey_t() { + memset(key, 0, EVP_MAX_KEY_LENGTH); + valid = false; + } + + bool operator==(const AesCtrKey_t& rhs) const { + return (0 == memcmp(key, rhs.key, EVP_MAX_KEY_LENGTH)) && + (valid == rhs.valid); + } + + bool IsValid() const { return valid; } +}; + +typedef char EncryptMarker_t[8]; +static EncryptMarker_t Marker = "Encrypt"; + +// long term: code_version could be used in a switch statement or factory +// parameter version 0 is 12 byte sha1 description hash, 128 bit (16 byte) +// nounce (assumed to be packed/byte aligned) +typedef struct { + uint8_t key_description_[EVP_MAX_MD_SIZE]; + uint8_t nonce_[AES_BLOCK_SIZE / 2]; // block size is 16 +} Prefix0_t; + +class AESBlockAccessCipherStream : public BlockAccessCipherStream { + public: + AESBlockAccessCipherStream(const AesCtrKey_t& key, uint8_t code_version, + uint8_t nonce[]) + : key_(key), code_version_(code_version) { + memcpy(&nonce_, nonce, AES_BLOCK_SIZE / 2); + } + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return AES_BLOCK_SIZE; }; + + protected: + // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. + virtual void AllocateScratch(std::string&) override{}; + + // Encrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status EncryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; + + // Decrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; + + AesCtrKey_t key_; + uint8_t code_version_; + uint8_t nonce_[AES_BLOCK_SIZE / 2]; +}; + +class CTREncryptionProvider2 : public EncryptionProvider { + public: + CTREncryptionProvider2() = delete; + + CTREncryptionProvider2(const CTREncryptionProvider&&) = delete; + + CTREncryptionProvider2(const Sha1Description_t& key_desc, + const AesCtrKey_t& key) + : valid_(false), key_desc_(key_desc), key_(key) { + valid_ = key_desc_.IsValid() && key_.IsValid(); + } + + CTREncryptionProvider2(const std::string& key_desc_str, + const uint8_t unformatted_key[], int bytes) + : valid_(false), key_desc_(key_desc_str), key_(unformatted_key, bytes) { + valid_ = key_desc_.IsValid() && key_.IsValid(); + } + + virtual size_t GetPrefixLength() override { + return sizeof(Prefix0_t) + sizeof(EncryptMarker_t); + } + + virtual Status CreateNewPrefix(const std::string& /*fname*/, char* prefix, + size_t prefixLength) override; + + virtual Status CreateCipherStream( + const std::string& /*fname*/, const EnvOptions& /*options*/, + Slice& /*prefix*/, + std::unique_ptr* /*result*/) override { + return Status::NotSupported("Wrong EncryptionProvider assumed"); + } + + virtual BlockAccessCipherStream* CreateCipherStream2(uint8_t code_version, + uint8_t nonce[]) { + return new AESBlockAccessCipherStream(key_, code_version, nonce); + } + + bool Valid() const { return valid_; }; + const Sha1Description_t& key_desc() const { return key_desc_; }; + const AesCtrKey_t& key() const { return key_; }; + + protected: + bool valid_; + Sha1Description_t key_desc_; + AesCtrKey_t key_; +}; + +// EncryptedEnv2 implements an Env wrapper that adds encryption to files stored +// on disk. + +class EncryptedEnv2 : public EnvWrapper { + public: + using WriteKey_t = + std::pair>; + using ReadKeys_t = + std::map>; + + static Env* Default(); + static Env* Default(ReadKeys_t encrypt_read, WriteKey_t encrypt_write); + + EncryptedEnv2(Env* base_env); + + EncryptedEnv2(Env* base_env, ReadKeys_t encrypt_read, + WriteKey_t encrypt_write); + + void SetKeys(ReadKeys_t encrypt_read, WriteKey_t encrypt_write); + + bool IsWriteEncrypted() const { return nullptr != encrypt_write_.second; } + + // NewSequentialFile opens a file for sequential reading. + virtual Status NewSequentialFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // NewRandomAccessFile opens a file for random read access. + virtual Status NewRandomAccessFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // NewWritableFile opens a file for sequential writing. + virtual Status NewWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // Create an object that writes to a new file with the specified + // name. Deletes any existing file with the same name and creates a + // new file. On success, stores a pointer to the new file in + // *result and returns OK. On failure stores nullptr in *result and + // returns non-OK. + // + // The returned file will only be accessed by one thread at a time. + virtual Status ReopenWritableFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // Reuse an existing file by renaming it and opening it as writable. + virtual Status ReuseWritableFile(const std::string& fname, + const std::string& old_fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // Open `fname` for random read and write, if file doesn't exist the file + // will be created. On success, stores a pointer to the new file in + // *result and returns OK. On failure returns non-OK. + // + // The returned file will only be accessed by one thread at a time. + virtual Status NewRandomRWFile(const std::string& fname, + std::unique_ptr* result, + const EnvOptions& options) override; + + // Store in *result the attributes of the children of the specified directory. + // In case the implementation lists the directory prior to iterating the files + // and files are concurrently deleted, the deleted files will be omitted from + // result. + // The name attributes are relative to "dir". + // Original contents of *results are dropped. + // Returns OK if "dir" exists and "*result" contains its children. + // NotFound if "dir" does not exist, the calling process does not have + // permission to access "dir", or if "dir" is invalid. + // IOError if an IO Error was encountered + virtual Status GetChildrenFileAttributes( + const std::string& dir, std::vector* result) override; + + // Store the size of fname in *file_size. + virtual Status GetFileSize(const std::string& fname, + uint64_t* file_size) override; + + // only needed for GetChildrenFileAttributes & GetFileSize + virtual Status GetEncryptionProvider( + const std::string& fname, std::shared_ptr& provider); + + template + Status ReadSeqEncryptionPrefix( + TypeFile* f, std::shared_ptr& provider, + std::unique_ptr& stream) { + Status status; + + provider.reset(); // nullptr for provider implies "no encryption" + stream.release(); + + // Look for encryption marker + EncryptMarker_t marker; + Slice marker_slice; + status = f->Read(sizeof(marker), &marker_slice, marker); + if (status.ok()) { + if (sizeof(marker) == marker_slice.size() && + marker_slice.starts_with(Marker)) { + // code_version currently unused + uint8_t code_version = (uint8_t)marker_slice[7]; + + Slice prefix_slice; + Prefix0_t prefix_buffer; + status = + f->Read(sizeof(Prefix0_t), &prefix_slice, (char*)&prefix_buffer); + if (status.ok() && sizeof(Prefix0_t) == prefix_slice.size()) { + Sha1Description_t desc(prefix_buffer.key_description_, + sizeof(prefix_buffer.key_description_)); + + auto it = encrypt_read_.find(desc); + if (encrypt_read_.end() != it) { + CTREncryptionProvider2* ptr = + (CTREncryptionProvider2*)it->second.get(); + provider = it->second; + stream.reset(new AESBlockAccessCipherStream( + ptr->key(), code_version, prefix_buffer.nonce_)); + } else { + status = Status::NotSupported( + "No encryption key found to match input file"); + } + } + } + } + return status; + } + + template + Status ReadRandEncryptionPrefix( + TypeFile* f, std::shared_ptr& provider, + std::unique_ptr& stream) { + Status status; + + provider.reset(); // nullptr for provider implies "no encryption" + stream.release(); + + // Look for encryption marker + EncryptMarker_t marker; + Slice marker_slice; + status = f->Read(0, sizeof(marker), &marker_slice, marker); + if (status.ok()) { + if (sizeof(marker) == marker_slice.size() && + marker_slice.starts_with(Marker)) { + // code_version currently unused + uint8_t code_version = (uint8_t)marker_slice[7]; + + Slice prefix_slice; + Prefix0_t prefix_buffer; + status = f->Read(sizeof(marker), sizeof(Prefix0_t), &prefix_slice, + (char*)&prefix_buffer); + if (status.ok() && sizeof(Prefix0_t) == prefix_slice.size()) { + Sha1Description_t desc(prefix_buffer.key_description_, + sizeof(prefix_buffer.key_description_)); + + auto it = encrypt_read_.find(desc); + if (encrypt_read_.end() != it) { + CTREncryptionProvider2* ptr = + (CTREncryptionProvider2*)it->second.get(); + provider = it->second; + stream.reset(new AESBlockAccessCipherStream( + ptr->key(), code_version, prefix_buffer.nonce_)); + } else { + status = Status::NotSupported( + "No encryption key found to match input file"); + } + } + } + } + return status; + } + + template + Status WriteSeqEncryptionPrefix( + TypeFile* f, std::unique_ptr& stream) { + Status status; + + // set up Encryption maker, code version '0' + uint8_t code_version = {'0'}; + Prefix0_t prefix; + EncryptMarker_t marker; + strncpy(marker, Marker, sizeof(Marker)); + marker[sizeof(EncryptMarker_t) - 1] = code_version; + + Slice marker_slice(marker, sizeof(EncryptMarker_t)); + status = f->Append(marker_slice); + + if (status.ok()) { + // create nonce, then write it and key description + Slice prefix_slice((char*)&prefix, sizeof(prefix)); + + status = encrypt_write_.second->CreateNewPrefix( + std::string(), (char*)&prefix, + encrypt_write_.second->GetPrefixLength()); + + if (status.ok()) { + status = f->Append(prefix_slice); + } + } + + if (status.ok()) { + CTREncryptionProvider2* ptr = + (CTREncryptionProvider2*)encrypt_write_.second.get(); + stream.reset(new AESBlockAccessCipherStream(ptr->key(), code_version, + prefix.nonce_)); + } + + return status; + } + + template + Status WriteRandEncryptionPrefix( + TypeFile* f, std::unique_ptr& stream) { + Status status; + + // set up Encryption maker, code version '0' + uint8_t code_version = {'0'}; + Prefix0_t prefix; + EncryptMarker_t marker; + strncpy(marker, Marker, sizeof(Marker)); + marker[sizeof(EncryptMarker_t) - 1] = code_version; + + Slice marker_slice(marker, sizeof(EncryptMarker_t)); + status = f->Write(0, marker_slice); + + if (status.ok()) { + // create nonce, then write it and key description + Slice prefix_slice((char*)&prefix, sizeof(prefix)); + + status = encrypt_write_.second->CreateNewPrefix( + std::string(), (char*)&prefix, + encrypt_write_.second->GetPrefixLength()); + + if (status.ok()) { + status = f->Write(sizeof(EncryptMarker_t), prefix_slice); + } + } + + if (status.ok()) { + CTREncryptionProvider2* ptr = + (CTREncryptionProvider2*)encrypt_write_.second.get(); + stream.reset(new AESBlockAccessCipherStream(ptr->key(), code_version, + prefix.nonce_)); + } + + return status; + } + + bool IsValid() const { return valid_; } + + static UnixLibCrypto crypto_; + + protected: + std::map> + encrypt_read_; + std::pair> + encrypt_write_; + + bool valid_; +}; + +// Returns an Env that encrypts data when stored on disk and decrypts data when +// read from disk. Prefer EncryptedEnv2::Default(). +Env* NewEncryptedEnv2(Env* base_env, EncryptedEnv2::ReadKeys_t encrypt_read, + EncryptedEnv2::WriteKey_t encrypt_write); + +#endif // ROCKSDB_LITE + +} // namespace rocksdb + +#endif // ROCKSDB_OPENSSL_AES_CTR diff --git a/include/rocksdb/env_encryption.h b/include/rocksdb/env_encryption.h index a6e919546..ff2898f4f 100644 --- a/include/rocksdb/env_encryption.h +++ b/include/rocksdb/env_encryption.h @@ -5,7 +5,7 @@ #pragma once -#if !defined(ROCKSDB_LITE) +#if !defined(ROCKSDB_LITE) #include @@ -15,184 +15,414 @@ namespace rocksdb { class EncryptionProvider; -// Returns an Env that encrypts data when stored on disk and decrypts data when +// Returns an Env that encrypts data when stored on disk and decrypts data when // read from disk. Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider); -// BlockAccessCipherStream is the base class for any cipher stream that -// supports random access at block level (without requiring data from other blocks). -// E.g. CTR (Counter operation mode) supports this requirement. +// BlockAccessCipherStream is the base class for any cipher stream that +// supports random access at block level (without requiring data from other +// blocks). E.g. CTR (Counter operation mode) supports this requirement. class BlockAccessCipherStream { - public: - virtual ~BlockAccessCipherStream() {}; + public: + virtual ~BlockAccessCipherStream(){}; - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() = 0; + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() = 0; - // Encrypt one or more (partial) blocks of data at the file offset. - // Length of data is given in dataSize. - virtual Status Encrypt(uint64_t fileOffset, char *data, size_t dataSize); + // Encrypt one or more (partial) blocks of data at the file offset. + // Length of data is given in dataSize. + virtual Status Encrypt(uint64_t fileOffset, char* data, size_t dataSize); - // Decrypt one or more (partial) blocks of data at the file offset. - // Length of data is given in dataSize. - virtual Status Decrypt(uint64_t fileOffset, char *data, size_t dataSize); + // Decrypt one or more (partial) blocks of data at the file offset. + // Length of data is given in dataSize. + virtual Status Decrypt(uint64_t fileOffset, char* data, size_t dataSize); - protected: - // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. - virtual void AllocateScratch(std::string&) = 0; + protected: + // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. + virtual void AllocateScratch(std::string&) = 0; - // Encrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status EncryptBlock(uint64_t blockIndex, char *data, char* scratch) = 0; + // Encrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status EncryptBlock(uint64_t blockIndex, char* data, + char* scratch) = 0; - // Decrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status DecryptBlock(uint64_t blockIndex, char *data, char* scratch) = 0; + // Decrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) = 0; }; -// BlockCipher +// BlockCipher class BlockCipher { - public: - virtual ~BlockCipher() {}; + public: + virtual ~BlockCipher(){}; - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() = 0; + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() = 0; - // Encrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Encrypt(char *data) = 0; + // Encrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Encrypt(char* data) = 0; - // Decrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Decrypt(char *data) = 0; + // Decrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Decrypt(char* data) = 0; }; // Implements a BlockCipher using ROT13. // -// Note: This is a sample implementation of BlockCipher, +// Note: This is a sample implementation of BlockCipher, // it is NOT considered safe and should NOT be used in production. class ROT13BlockCipher : public BlockCipher { - private: - size_t blockSize_; - public: - ROT13BlockCipher(size_t blockSize) - : blockSize_(blockSize) {} - virtual ~ROT13BlockCipher() {}; - - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() override { return blockSize_; } - - // Encrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Encrypt(char *data) override; - - // Decrypt a block of data. - // Length of data is equal to BlockSize(). - virtual Status Decrypt(char *data) override; + private: + size_t blockSize_; + + public: + ROT13BlockCipher(size_t blockSize) : blockSize_(blockSize) {} + virtual ~ROT13BlockCipher(){}; + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return blockSize_; } + + // Encrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Encrypt(char* data) override; + + // Decrypt a block of data. + // Length of data is equal to BlockSize(). + virtual Status Decrypt(char* data) override; }; -// CTRCipherStream implements BlockAccessCipherStream using an -// Counter operations mode. +// CTRCipherStream implements BlockAccessCipherStream using an +// Counter operations mode. // See https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation // -// Note: This is a possible implementation of BlockAccessCipherStream, +// Note: This is a possible implementation of BlockAccessCipherStream, // it is considered suitable for use. class CTRCipherStream final : public BlockAccessCipherStream { - private: - BlockCipher& cipher_; - std::string iv_; - uint64_t initialCounter_; - public: - CTRCipherStream(BlockCipher& c, const char *iv, uint64_t initialCounter) - : cipher_(c), iv_(iv, c.BlockSize()), initialCounter_(initialCounter) {}; - virtual ~CTRCipherStream() {}; - - // BlockSize returns the size of each block supported by this cipher stream. - virtual size_t BlockSize() override { return cipher_.BlockSize(); } - - protected: - // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. - virtual void AllocateScratch(std::string&) override; - - // Encrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status EncryptBlock(uint64_t blockIndex, char *data, char *scratch) override; - - // Decrypt a block of data at the given block index. - // Length of data is equal to BlockSize(); - virtual Status DecryptBlock(uint64_t blockIndex, char *data, char *scratch) override; + private: + BlockCipher& cipher_; + std::string iv_; + uint64_t initialCounter_; + + public: + CTRCipherStream(BlockCipher& c, const char* iv, uint64_t initialCounter) + : cipher_(c), iv_(iv, c.BlockSize()), initialCounter_(initialCounter){}; + virtual ~CTRCipherStream(){}; + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return cipher_.BlockSize(); } + + protected: + // Allocate scratch space which is passed to EncryptBlock/DecryptBlock. + virtual void AllocateScratch(std::string&) override; + + // Encrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status EncryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; + + // Decrypt a block of data at the given block index. + // Length of data is equal to BlockSize(); + virtual Status DecryptBlock(uint64_t blockIndex, char* data, + char* scratch) override; }; -// The encryption provider is used to create a cipher stream for a specific file. -// The returned cipher stream will be used for actual encryption/decryption -// actions. +// The encryption provider is used to create a cipher stream for a specific +// file. The returned cipher stream will be used for actual +// encryption/decryption actions. class EncryptionProvider { public: - virtual ~EncryptionProvider() {}; - - // GetPrefixLength returns the length of the prefix that is added to every file - // and used for storing encryption options. - // For optimal performance, the prefix length should be a multiple of - // the page size. - virtual size_t GetPrefixLength() = 0; - - // CreateNewPrefix initialized an allocated block of prefix memory - // for a new file. - virtual Status CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) = 0; - - // CreateCipherStream creates a block access cipher stream for a file given - // given name and options. - virtual Status CreateCipherStream( - const std::string& fname, const EnvOptions& options, Slice& prefix, - std::unique_ptr* result) = 0; + virtual ~EncryptionProvider(){}; + + // GetPrefixLength returns the length of the prefix that is added to every + // file and used for storing encryption options. For optimal performance, the + // prefix length should be a multiple of the page size. + virtual size_t GetPrefixLength() = 0; + + // CreateNewPrefix initialized an allocated block of prefix memory + // for a new file. + virtual Status CreateNewPrefix(const std::string& fname, char* prefix, + size_t prefixLength) = 0; + + // CreateCipherStream creates a block access cipher stream for a file given + // given name and options. + virtual Status CreateCipherStream( + const std::string& fname, const EnvOptions& options, Slice& prefix, + std::unique_ptr* result) = 0; }; -// This encryption provider uses a CTR cipher stream, with a given block cipher +// This encryption provider uses a CTR cipher stream, with a given block cipher // and IV. // -// Note: This is a possible implementation of EncryptionProvider, +// Note: This is a possible implementation of EncryptionProvider, // it is considered suitable for use, provided a safe BlockCipher is used. class CTREncryptionProvider : public EncryptionProvider { - private: - BlockCipher& cipher_; - protected: - const static size_t defaultPrefixLength = 4096; + private: + BlockCipher& cipher_; + + protected: + const static size_t defaultPrefixLength = 4096; + + public: + CTREncryptionProvider(BlockCipher& c) : cipher_(c){}; + virtual ~CTREncryptionProvider() {} + + // GetPrefixLength returns the length of the prefix that is added to every + // file + // and used for storing encryption options. + // For optimal performance, the prefix length should be a multiple of + // the page size. + virtual size_t GetPrefixLength() override; + + // CreateNewPrefix initialized an allocated block of prefix memory + // for a new file. + virtual Status CreateNewPrefix(const std::string& fname, char* prefix, + size_t prefixLength) override; + + // CreateCipherStream creates a block access cipher stream for a file given + // given name and options. + virtual Status CreateCipherStream( + const std::string& fname, const EnvOptions& options, Slice& prefix, + std::unique_ptr* result) override; + + protected: + // PopulateSecretPrefixPart initializes the data into a new prefix block + // that will be encrypted. This function will store the data in plain text. + // It will be encrypted later (before written to disk). + // Returns the amount of space (starting from the start of the prefix) + // that has been initialized. + virtual size_t PopulateSecretPrefixPart(char* prefix, size_t prefixLength, + size_t blockSize); + + // CreateCipherStreamFromPrefix creates a block access cipher stream for a + // file given + // given name and options. The given prefix is already decrypted. + virtual Status CreateCipherStreamFromPrefix( + const std::string& fname, const EnvOptions& options, + uint64_t initialCounter, const Slice& iv, const Slice& prefix, + std::unique_ptr* result); +}; + +class EncryptedSequentialFile : public SequentialFile { + private: + std::unique_ptr file_; + std::unique_ptr stream_; + uint64_t offset_; + size_t prefixLength_; + + public: + // Default ctor. Given underlying sequential file is supposed to be at + // offset == prefixLength. + EncryptedSequentialFile(SequentialFile* f, BlockAccessCipherStream* s, + size_t prefixLength) + : file_(f), + stream_(s), + offset_(prefixLength), + prefixLength_(prefixLength) {} + + // Read up to "n" bytes from the file. "scratch[0..n-1]" may be + // written by this routine. Sets "*result" to the data that was + // read (including if fewer than "n" bytes were successfully read). + // May set "*result" to point at data in "scratch[0..n-1]", so + // "scratch[0..n-1]" must be live when "*result" is used. + // If an error was encountered, returns a non-OK status. + // + // REQUIRES: External synchronization + virtual Status Read(size_t n, Slice* result, char* scratch) override; + + // Skip "n" bytes from the file. This is guaranteed to be no + // slower that reading the same data, but may be faster. + // + // If end of file is reached, skipping will stop at the end of the + // file, and Skip will return OK. + // + // REQUIRES: External synchronization + virtual Status Skip(uint64_t n) override; + + // Indicates the upper layers if the current SequentialFile implementation + // uses direct IO. + virtual bool use_direct_io() const override; + + // Use the returned alignment value to allocate + // aligned buffer for Direct I/O + virtual size_t GetRequiredBufferAlignment() const override; + + // Remove any kind of caching of data from the offset to offset+length + // of this file. If the length is 0, then it refers to the end of file. + // If the system is not caching the file contents, then this is a noop. + virtual Status InvalidateCache(size_t offset, size_t length) override; + + // Positioned Read for direct I/O + // If Direct I/O enabled, offset, n, and scratch should be properly aligned + virtual Status PositionedRead(uint64_t offset, size_t n, Slice* result, + char* scratch) override; +}; + +// A file abstraction for randomly reading the contents of a file. +class EncryptedRandomAccessFile : public RandomAccessFile { + private: + std::unique_ptr file_; + std::unique_ptr stream_; + size_t prefixLength_; + + public: + EncryptedRandomAccessFile(RandomAccessFile* f, BlockAccessCipherStream* s, + size_t prefixLength) + : file_(f), stream_(s), prefixLength_(prefixLength) {} + + // Read up to "n" bytes from the file starting at "offset". + // "scratch[0..n-1]" may be written by this routine. Sets "*result" + // to the data that was read (including if fewer than "n" bytes were + // successfully read). May set "*result" to point at data in + // "scratch[0..n-1]", so "scratch[0..n-1]" must be live when + // "*result" is used. If an error was encountered, returns a non-OK + // status. + // + // Safe for concurrent use by multiple threads. + // If Direct I/O enabled, offset, n, and scratch should be aligned properly. + virtual Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override; + + // Readahead the file starting from offset by n bytes for caching. + virtual Status Prefetch(uint64_t offset, size_t n) override; + + // Tries to get an unique ID for this file that will be the same each time + // the file is opened (and will stay the same while the file is open). + // Furthermore, it tries to make this ID at most "max_size" bytes. If such an + // ID can be created this function returns the length of the ID and places it + // in "id"; otherwise, this function returns 0, in which case "id" + // may not have been modified. + // + // This function guarantees, for IDs from a given environment, two unique ids + // cannot be made equal to each other by adding arbitrary bytes to one of + // them. That is, no unique ID is the prefix of another. + // + // This function guarantees that the returned ID will not be interpretable as + // a single varint. + // + // Note: these IDs are only valid for the duration of the process. + virtual size_t GetUniqueId(char* id, size_t max_size) const override; + + virtual void Hint(AccessPattern pattern) override; + + // Indicates the upper layers if the current RandomAccessFile implementation + // uses direct IO. + virtual bool use_direct_io() const override; + + // Use the returned alignment value to allocate + // aligned buffer for Direct I/O + virtual size_t GetRequiredBufferAlignment() const override; + + // Remove any kind of caching of data from the offset to offset+length + // of this file. If the length is 0, then it refers to the end of file. + // If the system is not caching the file contents, then this is a noop. + virtual Status InvalidateCache(size_t offset, size_t length) override; +}; + +// A file abstraction for sequential writing. The implementation +// must provide buffering since callers may append small fragments +// at a time to the file. +class EncryptedWritableFile : public WritableFileWrapper { + private: + std::unique_ptr file_; + std::unique_ptr stream_; + size_t prefixLength_; + + public: + // Default ctor. Prefix is assumed to be written already. + EncryptedWritableFile(WritableFile* f, BlockAccessCipherStream* s, + size_t prefixLength) + : WritableFileWrapper(f), + file_(f), + stream_(s), + prefixLength_(prefixLength) {} + + Status Append(const Slice& data) override; + + Status PositionedAppend(const Slice& data, uint64_t offset) override; + + // Indicates the upper layers if the current WritableFile implementation + // uses direct IO. + virtual bool use_direct_io() const override; + + // Use the returned alignment value to allocate + // aligned buffer for Direct I/O + virtual size_t GetRequiredBufferAlignment() const override; + + /* + * Get the size of valid data in the file. + */ + virtual uint64_t GetFileSize() override; + + // Truncate is necessary to trim the file to the correct size + // before closing. It is not always possible to keep track of the file + // size due to whole pages writes. The behavior is undefined if called + // with other writes to follow. + virtual Status Truncate(uint64_t size) override; + + // Remove any kind of caching of data from the offset to offset+length + // of this file. If the length is 0, then it refers to the end of file. + // If the system is not caching the file contents, then this is a noop. + // This call has no effect on dirty pages in the cache. + virtual Status InvalidateCache(size_t offset, size_t length) override; + + // Sync a file range with disk. + // offset is the starting byte of the file range to be synchronized. + // nbytes specifies the length of the range to be synchronized. + // This asks the OS to initiate flushing the cached data to disk, + // without waiting for completion. + // Default implementation does nothing. + virtual Status RangeSync(uint64_t offset, uint64_t nbytes) override; + + // PrepareWrite performs any necessary preparation for a write + // before the write actually occurs. This allows for pre-allocation + // of space on devices where it can result in less file + // fragmentation and/or less waste from over-zealous filesystem + // pre-allocation. + virtual void PrepareWrite(size_t offset, size_t len) override; + + // Pre-allocates space for a file. + virtual Status Allocate(uint64_t offset, uint64_t len) override; +}; + +// A file abstraction for random reading and writing. +class EncryptedRandomRWFile : public RandomRWFile { + private: + std::unique_ptr file_; + std::unique_ptr stream_; + size_t prefixLength_; public: - CTREncryptionProvider(BlockCipher& c) - : cipher_(c) {}; - virtual ~CTREncryptionProvider() {} - - // GetPrefixLength returns the length of the prefix that is added to every file - // and used for storing encryption options. - // For optimal performance, the prefix length should be a multiple of - // the page size. - virtual size_t GetPrefixLength() override; - - // CreateNewPrefix initialized an allocated block of prefix memory - // for a new file. - virtual Status CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) override; - - // CreateCipherStream creates a block access cipher stream for a file given - // given name and options. - virtual Status CreateCipherStream( - const std::string& fname, const EnvOptions& options, Slice& prefix, - std::unique_ptr* result) override; - - protected: - // PopulateSecretPrefixPart initializes the data into a new prefix block - // that will be encrypted. This function will store the data in plain text. - // It will be encrypted later (before written to disk). - // Returns the amount of space (starting from the start of the prefix) - // that has been initialized. - virtual size_t PopulateSecretPrefixPart(char *prefix, size_t prefixLength, size_t blockSize); - - // CreateCipherStreamFromPrefix creates a block access cipher stream for a file given - // given name and options. The given prefix is already decrypted. - virtual Status CreateCipherStreamFromPrefix( - const std::string& fname, const EnvOptions& options, - uint64_t initialCounter, const Slice& iv, const Slice& prefix, - std::unique_ptr* result); + EncryptedRandomRWFile(RandomRWFile* f, BlockAccessCipherStream* s, + size_t prefixLength) + : file_(f), stream_(s), prefixLength_(prefixLength) {} + + // Indicates if the class makes use of direct I/O + // If false you must pass aligned buffer to Write() + virtual bool use_direct_io() const override; + + // Use the returned alignment value to allocate + // aligned buffer for Direct I/O + virtual size_t GetRequiredBufferAlignment() const override; + + // Write bytes in `data` at offset `offset`, Returns Status::OK() on success. + // Pass aligned buffer when use_direct_io() returns true. + virtual Status Write(uint64_t offset, const Slice& data) override; + + // Read up to `n` bytes starting from offset `offset` and store them in + // result, provided `scratch` size should be at least `n`. + // Returns Status::OK() on success. + virtual Status Read(uint64_t offset, size_t n, Slice* result, + char* scratch) const override; + + virtual Status Flush() override; + + virtual Status Sync() override; + + virtual Status Fsync() override; + + virtual Status Close() override; }; } // namespace rocksdb diff --git a/include/rocksdb/perf_context.h b/include/rocksdb/perf_context.h index 3f125c213..dad4413b4 100644 --- a/include/rocksdb/perf_context.h +++ b/include/rocksdb/perf_context.h @@ -201,6 +201,12 @@ struct PerfContext { uint64_t env_lock_file_nanos; uint64_t env_unlock_file_nanos; uint64_t env_new_logger_nanos; + + // Time spent in encrypting data. Populated when EncryptedEnv is used. + uint64_t encrypt_data_nanos; + // Time spent in decrypting data. Populated when EncryptedEnv is used. + uint64_t decrypt_data_nanos; + std::map* level_to_perf_context = nullptr; bool per_level_perf_context_enabled = false; }; diff --git a/port/BUILD b/port/BUILD index f84f06e56..d79463fbf 100644 --- a/port/BUILD +++ b/port/BUILD @@ -12,6 +12,7 @@ PLATFORM_DEFINES = select({ "ROCKSDB_SCHED_GETCPU_PRESENT", "rocksdb_shared_EXPORTS", "NPERF_CONTEXT", + "ROCKSDB_OPENSSL_AES_CTR", ], "//:osx": [ "OS_MACOSX", @@ -20,6 +21,7 @@ PLATFORM_DEFINES = select({ "ROCKSDB_LIB_IO_POSIX", "rocksdb_shared_EXPORTS", "NPERF_CONTEXT", + "ROCKSDB_OPENSSL_AES_CTR", ], "//:windows": [ "OS_WIN", diff --git a/src.mk b/src.mk index 990aa2ab7..f32bf6945 100644 --- a/src.mk +++ b/src.mk @@ -61,6 +61,7 @@ LIB_SOURCES = \ env/env.cc \ env/env_chroot.cc \ env/env_encryption.cc \ + env/env_encrypt2.cc \ env/env_hdfs.cc \ env/env_posix.cc \ env/io_posix.cc \ @@ -146,6 +147,7 @@ LIB_SOURCES = \ util/filter_policy.cc \ util/hash.cc \ util/jemalloc_nodump_allocator.cc \ + util/library_loader.cc \ util/log_buffer.cc \ util/murmurhash.cc \ util/random.cc \ @@ -344,6 +346,7 @@ MAIN_SOURCES = \ db/write_callback_test.cc \ db/write_controller_test.cc \ env/env_basic_test.cc \ + env/env_encrypt2_test.cc \ env/env_test.cc \ env/mock_env_test.cc \ memtable/inlineskiplist_test.cc \ @@ -382,6 +385,7 @@ MAIN_SOURCES = \ util/dynamic_bloom_test.cc \ util/event_logger_test.cc \ util/filelock_test.cc \ + util/library_loader_test.cc \ util/log_write_bench.cc \ util/rate_limiter_test.cc \ util/repeatable_thread_test.cc \ diff --git a/util/BUILD b/util/BUILD index 4ef948ee0..63534c67f 100644 --- a/util/BUILD +++ b/util/BUILD @@ -376,6 +376,14 @@ constrained_test( ], ) +constrained_test( + name = "library_loader_test", + srcs = ["library_loader_test.cc"], + deps = [ + ":test_utils", + ":util", + ], +) constrained_test( name = "rate_limiter_test", srcs = ["rate_limiter_test.cc"], diff --git a/util/build_version.cc b/util/build_version.cc index b323f6aff..8465b15ea 100644 --- a/util/build_version.cc +++ b/util/build_version.cc @@ -1,4 +1,4 @@ #include "build_version.h" -const char* rocksdb_build_git_sha = "rocksdb_build_git_sha:REDACTED"; -const char* rocksdb_build_git_date = "rocksdb_build_git_date:REDACTED"; +const char* rocksdb_build_git_sha = "rocksdb_build_git_sha:42f9f56c6fc201804d3905deb5f6337669578220"; +const char* rocksdb_build_git_date = "rocksdb_build_git_date:2020-04-22"; const char* rocksdb_build_compile_date = __DATE__; diff --git a/util/library_loader.cc b/util/library_loader.cc new file mode 100644 index 000000000..081a929a2 --- /dev/null +++ b/util/library_loader.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +#include "util/library_loader.h" + +#include + +// link with -ldl + +namespace rocksdb { + +#ifdef OS_MACOSX + const char * UnixLibCrypto::crypto_lib_name_ = "libcrypto.dylib"; +#else + const char * UnixLibCrypto::crypto_lib_name_ = "libcrypto.so"; +#endif + +UnixLibraryLoader::UnixLibraryLoader(const char * library_name) + : dl_handle_(nullptr) { + + if (nullptr != library_name && '\0' != *library_name) { + dl_handle_ = dlopen(library_name, RTLD_NOW | RTLD_GLOBAL); + + is_valid_ = (nullptr != dl_handle_); + + if (!is_valid_) { + last_error_msg_ = dlerror(); + } + } +} + + +UnixLibraryLoader::~UnixLibraryLoader() { + if (nullptr != dl_handle_ ) { + int ret_val = dlclose(dl_handle_); + dl_handle_ = nullptr; + is_valid_ = false; + + if (0 != ret_val) { + last_error_msg_ = dlerror(); + } + } +} + + +void * UnixLibraryLoader::GetEntryPoint(const char * function_name) { + void * ret_ptr = {nullptr}; + + if (is_valid_) { + ret_ptr = dlsym(dl_handle_, function_name); + if (nullptr == ret_ptr) { + last_error_msg_ = dlerror(); + } + } + + return ret_ptr; +} + + +size_t UnixLibraryLoader::GetEntryPoints(std::map & functions) { + size_t num_found {0}; + + if (is_valid_) { + for (auto& func : functions) { + void * tmp_ptr; + + tmp_ptr = GetEntryPoint(func.first.c_str()); + if (nullptr != tmp_ptr) { + ++num_found; + func.second = tmp_ptr; + } + } + } + + return num_found; +} + +UnixLibCrypto::UnixLibCrypto() + : UnixLibraryLoader(crypto_lib_name_) { + if (is_valid_) { + // size of map minus two since _new/_create and _free/_destroy + // only resolve one of the two. + is_valid_ = ((functions_.size()-2) == GetEntryPoints(functions_)); + + ctx_new_ = (EVP_MD_CTX_new_t) functions_["EVP_MD_CTX_new"]; + if (nullptr == ctx_new_) { + ctx_new_ = (EVP_MD_CTX_new_t) functions_["EVP_MD_CTX_create"]; + } + + digest_init_ = (EVP_DigestInit_ex_t) functions_["EVP_DigestInit_ex"]; + sha1_ = (EVP_sha1_t) functions_["EVP_sha1"]; + digest_update_ = (EVP_DigestUpdate_t) functions_["EVP_DigestUpdate"]; + digest_final_ = (EVP_DigestFinal_ex_t) functions_["EVP_DigestFinal_ex"]; + + ctx_free_ = (EVP_MD_CTX_free_t) functions_["EVP_MD_CTX_free"]; + if (nullptr == ctx_free_) { + ctx_free_ = (EVP_MD_CTX_free_t) functions_["EVP_MD_CTX_destroy"]; + } + + rand_bytes_ = (RAND_bytes_t) functions_["RAND_bytes"]; + rand_poll_ = (RAND_poll_t) functions_["RAND_poll"]; + + cipher_new_ = (EVP_CIPHER_CTX_new_t) functions_["EVP_CIPHER_CTX_new"]; + cipher_free_ = (EVP_CIPHER_CTX_free_t) functions_["EVP_CIPHER_CTX_free"]; + encrypt_init_ = (EVP_EncryptInit_ex_t) functions_["EVP_EncryptInit_ex"]; + aes_256_ctr_ = (EVP_aes_256_ctr_t) functions_["EVP_aes_256_ctr"]; + encrypt_update_ = (EVP_EncryptUpdate_t) functions_["EVP_EncryptUpdate"]; + + } +} + +} // namespace rocksdb diff --git a/util/library_loader.h b/util/library_loader.h new file mode 100644 index 000000000..dd9cffd94 --- /dev/null +++ b/util/library_loader.h @@ -0,0 +1,169 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +#pragma once + +#include +#include +#include + +namespace rocksdb { + +// Base class / interface +// expectation is to derive one class for unux and one for Windows +// +class LibraryLoader { + public: + LibraryLoader() : is_valid_(false) {}; + virtual ~LibraryLoader() = default; + + bool IsValid() const {return is_valid_;} + + virtual void * GetEntryPoint(const char * function_name) = 0; + + protected: + bool is_valid_; +}; + + +class UnixLibraryLoader : public LibraryLoader { + public: + UnixLibraryLoader() = delete; + + UnixLibraryLoader(const char * library_name); + + virtual ~UnixLibraryLoader(); + + virtual void * GetEntryPoint(const char * function_name) override; + + virtual size_t GetEntryPoints(std::map & functions); + +protected: + void * dl_handle_; + std::string last_error_msg_; +}; + + +class UnixLibCrypto : public UnixLibraryLoader { +public: + UnixLibCrypto(); + virtual ~UnixLibCrypto() = default; + + // _new & _free are ssl 1.1, replacing 1.0 _create & _destroy + using EVP_MD_CTX_new_t = EVP_MD_CTX * (*)(void); + using EVP_DigestInit_ex_t = int (*)(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl); + using EVP_sha1_t = const EVP_MD * (*)(void); + using EVP_DigestUpdate_t = int (*)(EVP_MD_CTX *ctx, const void *d, size_t cnt); + using EVP_DigestFinal_ex_t = int (*)(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s); + using EVP_MD_CTX_free_t = void (*)(EVP_MD_CTX *ctx); + + EVP_MD_CTX * EVP_MD_CTX_new() const {return ctx_new_();}; + + int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl) { + return digest_init_(ctx, type, impl); + } + + const EVP_MD * EVP_sha1() {return sha1_();} + + int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt) { + return digest_update_(ctx, d, cnt); + } + + int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s) { + return digest_final_(ctx, md, s); + } + + void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { + ctx_free_(ctx); + } + + EVP_MD_CTX_free_t EVP_MD_CTX_free_ptr() { + return ctx_free_; + } + + using RAND_bytes_t = int (*)(unsigned char *buf, int num); + using RAND_poll_t = int (*)(); + + int RAND_bytes(unsigned char *buf, int num) { + return rand_bytes_(buf, num); + } + + int RAND_poll() { + return rand_poll_(); + } + + using EVP_CIPHER_CTX_new_t = EVP_CIPHER_CTX * (*)(void); + using EVP_CIPHER_CTX_free_t = void (*)(EVP_CIPHER_CTX *ctx); + using EVP_EncryptInit_ex_t = int (*)(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, + ENGINE *impl, const unsigned char *key, const unsigned char *iv); + using EVP_aes_256_ctr_t = const EVP_CIPHER * (*)(void); + using EVP_EncryptUpdate_t = int (*)(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl, const unsigned char *in, int inl); + + EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void) const {return cipher_new_();}; + + void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) { + cipher_free_(ctx); + } + + EVP_CIPHER_CTX_free_t EVP_CIPHER_CTX_free_ptr() { + return cipher_free_; + } + + int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, + ENGINE *impl, const unsigned char *key, const unsigned char *iv) { + return encrypt_init_(ctx, type, impl, key, iv); + } + + const EVP_CIPHER * EVP_aes_256_ctr() { + return aes_256_ctr_(); + } + + int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, + int *outl, const unsigned char *in, int inl) { + return encrypt_update_(ctx, out, outl, in, inl); + } + + static const char * crypto_lib_name_; + +protected: + std::map functions_ { + {"EVP_MD_CTX_new", nullptr}, {"EVP_MD_CTX_create", nullptr}, + {"EVP_DigestInit_ex", nullptr}, + {"EVP_sha1", nullptr}, + {"EVP_DigestUpdate", nullptr}, + {"EVP_DigestFinal_ex", nullptr}, + {"EVP_MD_CTX_free", nullptr}, {"EVP_MD_CTX_destroy", nullptr}, + + {"RAND_bytes", nullptr}, + {"RAND_poll", nullptr}, + + {"EVP_CIPHER_CTX_new", nullptr}, + {"EVP_CIPHER_CTX_free", nullptr}, + {"EVP_EncryptInit_ex", nullptr}, + {"EVP_aes_256_ctr", nullptr}, + {"EVP_EncryptUpdate", nullptr}, + + }; + + EVP_MD_CTX_new_t ctx_new_; + EVP_DigestInit_ex_t digest_init_; + EVP_sha1_t sha1_; + EVP_DigestUpdate_t digest_update_; + EVP_DigestFinal_ex_t digest_final_; + EVP_MD_CTX_free_t ctx_free_; + + RAND_bytes_t rand_bytes_; + RAND_poll_t rand_poll_; + + EVP_CIPHER_CTX_new_t cipher_new_; + EVP_CIPHER_CTX_free_t cipher_free_; + EVP_EncryptInit_ex_t encrypt_init_; + EVP_aes_256_ctr_t aes_256_ctr_; + EVP_EncryptUpdate_t encrypt_update_; + +}; + +} // namespace rocksdb diff --git a/util/library_loader_test.cc b/util/library_loader_test.cc new file mode 100644 index 000000000..cb8e094bd --- /dev/null +++ b/util/library_loader_test.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +#include + +#include "util/library_loader.h" + +namespace rocksdb { + +#ifdef OS_MACOSX + static const char * LIB_M_NAME = "libm.dylib"; + static const char * LIB_BAD_NAME = "libbubbagump.dylib"; + static const char * LIB_SSL_NAME = "libssl.dylib"; +#else + static const char * LIB_M_NAME = "libm.so.6"; + static const char * LIB_BAD_NAME = "libbubbagump.so"; + static const char * LIB_SSL_NAME = "libssl.so"; +#endif + + +class UnixLibraryLoaderTest {}; + +TEST(UnixLibraryLoaderTest, Simple) { + + UnixLibraryLoader works(LIB_M_NAME); + UnixLibraryLoader fails(LIB_BAD_NAME); + + ASSERT_TRUE(works.IsValid()); + ASSERT_FALSE(fails.IsValid()); + + double (*floor)(double); + + floor = (double (*)(double))works.GetEntryPoint("floor"); + ASSERT_TRUE(nullptr != floor); + ASSERT_TRUE(2.0 == (*floor)(2.2)); + +} + +TEST(UnixLibraryLoaderTest, SSL) { + UnixLibraryLoader ssl(LIB_SSL_NAME); + UnixLibraryLoader crypto(UnixLibCrypto::crypto_lib_name_); + + ASSERT_TRUE(ssl.IsValid()); + ASSERT_TRUE(crypto.IsValid()); + +} + +TEST(UnixLibraryLoaderTest, Crypto) { + UnixLibCrypto crypto; + uint8_t desc[EVP_MAX_MD_SIZE]; + EVP_MD_CTX * context; + int ret_val; + unsigned len; + + ASSERT_TRUE(crypto.IsValid()); + + context = crypto.EVP_MD_CTX_create(); + ASSERT_TRUE(nullptr != context); + + ret_val = crypto.EVP_DigestInit_ex(context, crypto.EVP_sha1(), nullptr); + ASSERT_TRUE(1 == ret_val); + + ret_val = crypto.EVP_DigestUpdate(context, "1", 1); + ASSERT_TRUE(1 == ret_val); + + ret_val = crypto.EVP_DigestFinal_ex(context, desc, &len); + ASSERT_TRUE(1 == ret_val); + ASSERT_TRUE(20 == len); + + uint8_t md2[] = {0x35, 0x6a, 0x19, 0x2b, 0x79, 0x13, 0xb0, 0x4c, + 0x54, 0x57, 0x4d, 0x18, 0xc2, 0x8d, 0x46, 0xe6, + 0x39, 0x54, 0x28, 0xab}; + ASSERT_TRUE(0 == memcmp(md2, desc, sizeof(md2))); + + crypto.EVP_MD_CTX_free(context); + +} + + +} // namespace rocksdb + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/util/sync_point.h b/util/sync_point.h index 7aa114f2f..b9767d3d3 100644 --- a/util/sync_point.h +++ b/util/sync_point.h @@ -130,12 +130,12 @@ class SyncPoint { // utilized to re-produce race conditions between threads. // See TransactionLogIteratorRace in db_test.cc for an example use case. // TEST_SYNC_POINT is no op in release build. -#define TEST_SYNC_POINT(x) - //rocksdb::SyncPoint::GetInstance()->Process(x) +#define TEST_SYNC_POINT(x) \ + rocksdb::SyncPoint::GetInstance()->Process(x) #define TEST_IDX_SYNC_POINT(x, index) \ - //rocksdb::SyncPoint::GetInstance()->Process(x + std::to_string(index)) + rocksdb::SyncPoint::GetInstance()->Process(x + std::to_string(index)) #define TEST_SYNC_POINT_CALLBACK(x, y) \ - //rocksdb::SyncPoint::GetInstance()->Process(x, y) + rocksdb::SyncPoint::GetInstance()->Process(x, y) #define INIT_SYNC_POINT_SINGLETONS() \ (void)rocksdb::SyncPoint::GetInstance(); #endif // NDEBUG