Changes to EncryptedEnv public API (#7279)

Summary:
Cleaned up the public API to use the EncryptedEnv.  This change will allow providers to be developed and added to the system easier in the future.  It will also allow better integration in the future with the OPTIONS file.

- The internal classes were moved out of the public API into an internal "env_encryption_ctr.h" header.  Short-cut constructors were added to provide the original API functionality.
- The APIs to the constructors were changed to take shared_ptr, rather than raw pointers or references to allow better memory management and alternative implementations.
- CreateFromString methods were added to allow future expansion to other provider and cipher implementations through a standard API.

Additionally, there was a code duplication in the NewXXXFile methods.  This common code was moved under a templatized function.

A first-pass at structuring the code was made to potentially allow multiple EncryptionProviders in a single EncryptedEnv.  The idea was that different providers may use different cipher keys or different versions/algorithms.  The EncryptedEnv should have some means of picking different providers based on information.  The groundwork was started for this (the use of the provider_ member variable was localized) but the work has not been completed.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/7279

Reviewed By: jay-zhuang

Differential Revision: D23709440

Pulled By: zhichao-cao

fbshipit-source-id: 0e845fff0e03a52603eb9672b4ade32d063ff2f2
This commit is contained in:
mrambacher 2020-09-15 15:12:58 -07:00 committed by Facebook GitHub Bot
parent b0e7834100
commit 67bd5401e9
6 changed files with 624 additions and 327 deletions

View File

@ -35,6 +35,11 @@
### Others
* Error in prefetching partitioned index blocks will not be swallowed. It will fail the query and return the IOError users.
### Public API Change
* The methods to create and manage EncrypedEnv have been changed. The EncryptionProvider is now passed to NewEncryptedEnv as a shared pointer, rather than a raw pointer. Comparably, the CTREncryptedProvider now takes a shared pointer, rather than a reference, to a BlockCipher. CreateFromString methods have been added to BlockCipher and EncryptionProvider to provide a single API by which different ciphers and providers can be created, respectively.
* The internal classes (CTREncryptionProvider, ROT13BlockCipher, CTRCipherStream) associated with the EncryptedEnv have been moved out of the public API. To create a CTREncryptionProvider, one can either use EncryptionProvider::NewCTRProvider, or EncryptionProvider::CreateFromString("CTR"). To create a new ROT13BlockCipher, one can either use BlockCipher::NewROT13Cipher or BlockCipher::CreateFromString("ROT13").
* The EncryptionProvider::AddCipher method has been added to allow keys to be added to an EncryptionProvider. This API will allow future providers to support multiple cipher keys.
## 6.12 (2020-07-28)
### Public API Change
* Encryption file classes now exposed for inheritance in env_encryption.h

View File

@ -10,6 +10,7 @@
#include "db/db_test_util.h"
#include "db/forward_iterator.h"
#include "rocksdb/convenience.h"
#include "rocksdb/env_encryption.h"
#include "rocksdb/utilities/object_registry.h"
#include "util/random.h"
@ -53,10 +54,6 @@ SpecialEnv::SpecialEnv(Env* base, bool time_elapse_only_sleep)
non_writable_count_ = 0;
table_write_callback_ = nullptr;
}
#ifndef ROCKSDB_LITE
ROT13BlockCipher rot13Cipher_(16);
#endif // ROCKSDB_LITE
DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
: mem_env_(nullptr), encrypted_env_(nullptr), option_config_(kDefault) {
Env* base_env = Env::Default();
@ -76,8 +73,11 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
}
#ifndef ROCKSDB_LITE
if (getenv("ENCRYPTED_ENV")) {
encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env,
new CTREncryptionProvider(rot13Cipher_));
std::shared_ptr<EncryptionProvider> provider;
Status s = EncryptionProvider::CreateFromString(
ConfigOptions(), std::string("test://") + getenv("ENCRYPTED_ENV"),
&provider);
encrypted_env_ = NewEncryptedEnv(mem_env_ ? mem_env_ : base_env, provider);
}
#endif // !ROCKSDB_LITE
env_ = new SpecialEnv(encrypted_env_ ? encrypted_env_

29
env/env_basic_test.cc vendored
View File

@ -10,6 +10,7 @@
#include <vector>
#include "env/mock_env.h"
#include "rocksdb/convenience.h"
#include "rocksdb/env.h"
#include "rocksdb/env_encryption.h"
#include "test_util/testharness.h"
@ -19,7 +20,12 @@ namespace ROCKSDB_NAMESPACE {
// Normalizes trivial differences across Envs such that these test cases can
// run on all Envs.
class NormalizingEnvWrapper : public EnvWrapper {
private:
std::unique_ptr<Env> base_;
public:
explicit NormalizingEnvWrapper(std::unique_ptr<Env>&& base)
: EnvWrapper(base.get()), base_(std::move(base)) {}
explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {}
// Removes . and .. from directory listing
@ -94,20 +100,21 @@ INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam,
::testing::Values(mock_env.get()));
#ifndef ROCKSDB_LITE
static Env* NewTestEncryptedEnv(Env* base, const std::string& provider_id) {
std::shared_ptr<EncryptionProvider> provider;
EXPECT_OK(EncryptionProvider::CreateFromString(ConfigOptions(), provider_id,
&provider));
std::unique_ptr<Env> encrypted(NewEncryptedEnv(base, provider));
return new NormalizingEnvWrapper(std::move(encrypted));
}
// 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<Env> ecpt_env(NewEncryptedEnv(Env::Default(),
&encrypt_provider_ctr));
static std::unique_ptr<Env> encrypt_env(
new NormalizingEnvWrapper(ecpt_env.get()));
static std::unique_ptr<Env> ctr_encrypt_env(NewTestEncryptedEnv(Env::Default(),
"test://CTR"));
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvBasicTestWithParam,
::testing::Values(encrypt_env.get()));
::testing::Values(ctr_encrypt_env.get()));
INSTANTIATE_TEST_CASE_P(EncryptedEnv, EnvMoreTestWithParam,
::testing::Values(encrypt_env.get()));
::testing::Values(ctr_encrypt_env.get()));
#endif // ROCKSDB_LITE
#ifndef ROCKSDB_LITE

577
env/env_encryption.cc vendored
View File

@ -12,17 +12,69 @@
#include <cctype>
#include <iostream>
#include "env/env_encryption_ctr.h"
#include "monitoring/perf_context_imp.h"
#include "rocksdb/convenience.h"
#include "util/aligned_buffer.h"
#include "util/coding.h"
#include "util/random.h"
#include "util/string_util.h"
#endif
namespace ROCKSDB_NAMESPACE {
#ifndef ROCKSDB_LITE
static constexpr char kROT13CipherName[] = "ROT13";
static constexpr char kCTRProviderName[] = "CTR";
Status BlockCipher::CreateFromString(const ConfigOptions& /*config_options*/,
const std::string& value,
std::shared_ptr<BlockCipher>* result) {
std::string id = value;
size_t colon = value.find(':');
if (colon != std::string::npos) {
id = value.substr(0, colon);
}
if (id == kROT13CipherName) {
if (colon != std::string::npos) {
size_t block_size = ParseSizeT(value.substr(colon + 1));
result->reset(new ROT13BlockCipher(block_size));
} else {
result->reset(new ROT13BlockCipher(32));
}
return Status::OK();
} else {
return Status::NotSupported("Could not find cipher ", value);
}
}
Status EncryptionProvider::CreateFromString(
const ConfigOptions& /*config_options*/, const std::string& value,
std::shared_ptr<EncryptionProvider>* result) {
std::string id = value;
bool is_test = StartsWith(value, "test://");
Status status = Status::OK();
if (is_test) {
id = value.substr(strlen("test://"));
}
if (id == kCTRProviderName) {
result->reset(new CTREncryptionProvider());
} else if (is_test) {
result->reset(new CTREncryptionProvider());
} else {
return Status::NotSupported("Could not find provider ", value);
}
if (status.ok() && is_test) {
status = result->get()->TEST_Initialize();
}
return status;
}
std::shared_ptr<EncryptionProvider> EncryptionProvider::NewCTRProvider(
const std::shared_ptr<BlockCipher>& cipher) {
return std::make_shared<CTREncryptionProvider>(cipher);
}
// 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
@ -361,9 +413,221 @@ Status EncryptedRandomRWFile::Close() { return file_->Close(); }
// EncryptedEnv implements an Env wrapper that adds encryption to files stored
// on disk.
class EncryptedEnv : public EnvWrapper {
class EncryptedEnvImpl : public EnvWrapper {
// Returns the raw encryption provider that should be used to write the input
// encrypted file. If there is no such provider, NotFound is returned.
Status GetWritableProvider(const std::string& /*fname*/,
EncryptionProvider** result) {
if (provider_) {
*result = provider_.get();
return Status::OK();
} else {
*result = nullptr;
return Status::NotFound("No WriteProvider specified");
}
}
// Returns the raw encryption provider that should be used to read the input
// encrypted file. If there is no such provider, NotFound is returned.
Status GetReadableProvider(const std::string& /*fname*/,
EncryptionProvider** result) {
if (provider_) {
*result = provider_.get();
return Status::OK();
} else {
*result = nullptr;
return Status::NotFound("No Provider specified");
}
}
// Creates a CipherStream for the underlying file/name using the options
// If a writable provider is found and encryption is enabled, uses
// this provider to create a cipher stream.
// @param fname Name of the writable file
// @param underlying The underlying "raw" file
// @param options Options for creating the file/cipher
// @param prefix_length Returns the length of the encryption prefix used for
// this file
// @param stream Returns the cipher stream to use for this file if it
// should be encrypted
// @return OK on success, non-OK on failure.
template <class TypeFile>
Status CreateWritableCipherStream(
const std::string& fname, const std::unique_ptr<TypeFile>& underlying,
const EnvOptions& options, size_t* prefix_length,
std::unique_ptr<BlockAccessCipherStream>* stream) {
EncryptionProvider* provider = nullptr;
*prefix_length = 0;
Status status = GetWritableProvider(fname, &provider);
if (!status.ok()) {
return status;
} else if (provider != nullptr) {
// Initialize & write prefix (if needed)
AlignedBuffer buffer;
Slice prefix;
*prefix_length = provider->GetPrefixLength();
if (*prefix_length > 0) {
// Initialize prefix
buffer.Alignment(underlying->GetRequiredBufferAlignment());
buffer.AllocateNewBuffer(*prefix_length);
status = provider->CreateNewPrefix(fname, buffer.BufferStart(),
*prefix_length);
if (status.ok()) {
buffer.Size(*prefix_length);
prefix = Slice(buffer.BufferStart(), buffer.CurrentSize());
// Write prefix
status = underlying->Append(prefix);
}
if (!status.ok()) {
return status;
}
}
// Create cipher stream
status = provider->CreateCipherStream(fname, options, prefix, stream);
}
return status;
}
template <class TypeFile>
Status CreateWritableEncryptedFile(const std::string& fname,
std::unique_ptr<TypeFile>& underlying,
const EnvOptions& options,
std::unique_ptr<TypeFile>* result) {
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
size_t prefix_length;
Status status = CreateWritableCipherStream(fname, underlying, options,
&prefix_length, &stream);
if (status.ok()) {
if (stream) {
result->reset(new EncryptedWritableFile(
std::move(underlying), std::move(stream), prefix_length));
} else {
result->reset(underlying.release());
}
}
return status;
}
// Creates a CipherStream for the underlying file/name using the options
// If a writable provider is found and encryption is enabled, uses
// this provider to create a cipher stream.
// @param fname Name of the writable file
// @param underlying The underlying "raw" file
// @param options Options for creating the file/cipher
// @param prefix_length Returns the length of the encryption prefix used for
// this file
// @param stream Returns the cipher stream to use for this file if it
// should be encrypted
// @return OK on success, non-OK on failure.
template <class TypeFile>
Status CreateRandomWriteCipherStream(
const std::string& fname, const std::unique_ptr<TypeFile>& underlying,
const EnvOptions& options, size_t* prefix_length,
std::unique_ptr<BlockAccessCipherStream>* stream) {
EncryptionProvider* provider = nullptr;
*prefix_length = 0;
Status status = GetWritableProvider(fname, &provider);
if (!status.ok()) {
return status;
} else if (provider != nullptr) {
// Initialize & write prefix (if needed)
AlignedBuffer buffer;
Slice prefix;
*prefix_length = provider->GetPrefixLength();
if (*prefix_length > 0) {
// Initialize prefix
buffer.Alignment(underlying->GetRequiredBufferAlignment());
buffer.AllocateNewBuffer(*prefix_length);
status = provider->CreateNewPrefix(fname, buffer.BufferStart(),
*prefix_length);
if (status.ok()) {
buffer.Size(*prefix_length);
prefix = Slice(buffer.BufferStart(), buffer.CurrentSize());
// Write prefix
status = underlying->Write(0, prefix);
}
if (!status.ok()) {
return status;
}
}
// Create cipher stream
status = provider->CreateCipherStream(fname, options, prefix, stream);
}
return status;
}
// Creates a CipherStream for the underlying file/name using the options
// If a readable provider is found and the file is encrypted, uses
// this provider to create a cipher stream.
// @param fname Name of the writable file
// @param underlying The underlying "raw" file
// @param options Options for creating the file/cipher
// @param prefix_length Returns the length of the encryption prefix used for
// this file
// @param stream Returns the cipher stream to use for this file if it
// is encrypted
// @return OK on success, non-OK on failure.
template <class TypeFile>
Status CreateSequentialCipherStream(
const std::string& fname, const std::unique_ptr<TypeFile>& underlying,
const EnvOptions& options, size_t* prefix_length,
std::unique_ptr<BlockAccessCipherStream>* stream) {
// Read prefix (if needed)
AlignedBuffer buffer;
Slice prefix;
*prefix_length = provider_->GetPrefixLength();
if (*prefix_length > 0) {
// Read prefix
buffer.Alignment(underlying->GetRequiredBufferAlignment());
buffer.AllocateNewBuffer(*prefix_length);
Status status =
underlying->Read(*prefix_length, &prefix, buffer.BufferStart());
if (!status.ok()) {
return status;
}
buffer.Size(*prefix_length);
}
return provider_->CreateCipherStream(fname, options, prefix, stream);
}
// Creates a CipherStream for the underlying file/name using the options
// If a readable provider is found and the file is encrypted, uses
// this provider to create a cipher stream.
// @param fname Name of the writable file
// @param underlying The underlying "raw" file
// @param options Options for creating the file/cipher
// @param prefix_length Returns the length of the encryption prefix used for
// this file
// @param stream Returns the cipher stream to use for this file if it
// is encrypted
// @return OK on success, non-OK on failure.
template <class TypeFile>
Status CreateRandomReadCipherStream(
const std::string& fname, const std::unique_ptr<TypeFile>& underlying,
const EnvOptions& options, size_t* prefix_length,
std::unique_ptr<BlockAccessCipherStream>* stream) {
// Read prefix (if needed)
AlignedBuffer buffer;
Slice prefix;
*prefix_length = provider_->GetPrefixLength();
if (*prefix_length > 0) {
// Read prefix
buffer.Alignment(underlying->GetRequiredBufferAlignment());
buffer.AllocateNewBuffer(*prefix_length);
Status status =
underlying->Read(0, *prefix_length, &prefix, buffer.BufferStart());
if (!status.ok()) {
return status;
}
buffer.Size(*prefix_length);
}
return provider_->CreateCipherStream(fname, options, prefix, stream);
}
public:
EncryptedEnv(Env* base_env, EncryptionProvider* provider)
EncryptedEnvImpl(Env* base_env,
const std::shared_ptr<EncryptionProvider>& provider)
: EnvWrapper(base_env) {
provider_ = provider;
}
@ -382,31 +646,16 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Read prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
// Read prefix
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
status =
underlying->Read(prefixLength, &prefixSlice, prefixBuf.BufferStart());
if (!status.ok()) {
return status;
}
prefixBuf.Size(prefixLength);
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
size_t prefix_length;
status = CreateSequentialCipherStream(fname, underlying, options,
&prefix_length, &stream);
if (status.ok()) {
result->reset(new EncryptedSequentialFile(
std::move(underlying), std::move(stream), prefix_length));
}
(*result) = std::unique_ptr<SequentialFile>(new EncryptedSequentialFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
return status;
}
// NewRandomAccessFile opens a file for random read access.
@ -423,31 +672,19 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Read prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
// Read prefix
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
status = underlying->Read(0, prefixLength, &prefixSlice,
prefixBuf.BufferStart());
if (!status.ok()) {
return status;
}
prefixBuf.Size(prefixLength);
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
size_t prefix_length;
status = CreateRandomReadCipherStream(fname, underlying, options,
&prefix_length, &stream);
if (status.ok()) {
if (stream) {
result->reset(new EncryptedRandomAccessFile(
std::move(underlying), std::move(stream), prefix_length));
} else {
result->reset(underlying.release());
}
}
(*result) = std::unique_ptr<RandomAccessFile>(new EncryptedRandomAccessFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
return status;
}
// NewWritableFile opens a file for sequential writing.
@ -464,36 +701,7 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Initialize & write prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
// Initialize prefix
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
status = provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(),
prefixLength);
if (status.ok()) {
prefixBuf.Size(prefixLength);
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
// Write prefix
status = underlying->Append(prefixSlice);
}
if (!status.ok()) {
return status;
}
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
}
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
return CreateWritableEncryptedFile(fname, underlying, options, result);
}
// Create an object that writes to a new file with the specified
@ -516,33 +724,7 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Initialize & write prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
// Initialize prefix
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
prefixBuf.Size(prefixLength);
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
// Write prefix
status = underlying->Append(prefixSlice);
if (!status.ok()) {
return status;
}
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
}
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
return CreateWritableEncryptedFile(fname, underlying, options, result);
}
// Reuse an existing file by renaming it and opening it as writable.
@ -561,33 +743,7 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Initialize & write prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
// Initialize prefix
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
provider_->CreateNewPrefix(fname, prefixBuf.BufferStart(), prefixLength);
prefixBuf.Size(prefixLength);
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
// Write prefix
status = underlying->Append(prefixSlice);
if (!status.ok()) {
return status;
}
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
}
(*result) = std::unique_ptr<WritableFile>(new EncryptedWritableFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
return CreateWritableEncryptedFile(fname, underlying, options, result);
}
// Open `fname` for random read and write, if file doesn't exist the file
@ -611,44 +767,26 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
// Read or Initialize & write prefix (if needed)
AlignedBuffer prefixBuf;
Slice prefixSlice;
size_t prefixLength = provider_->GetPrefixLength();
if (prefixLength > 0) {
prefixBuf.Alignment(underlying->GetRequiredBufferAlignment());
prefixBuf.AllocateNewBuffer(prefixLength);
if (!isNewFile) {
// File already exists, read prefix
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);
prefixBuf.Size(prefixLength);
prefixSlice = Slice(prefixBuf.BufferStart(), prefixBuf.CurrentSize());
// Write prefix
status = underlying->Write(0, prefixSlice);
if (!status.ok()) {
return status;
}
}
}
// Create cipher stream
std::unique_ptr<BlockAccessCipherStream> stream;
status =
provider_->CreateCipherStream(fname, options, prefixSlice, &stream);
if (!status.ok()) {
return status;
size_t prefix_length = 0;
if (!isNewFile) {
// File already exists, read prefix
status = CreateRandomReadCipherStream(fname, underlying, options,
&prefix_length, &stream);
} else {
status = CreateRandomWriteCipherStream(fname, underlying, options,
&prefix_length, &stream);
}
(*result) = std::unique_ptr<RandomRWFile>(new EncryptedRandomRWFile(
std::move(underlying), std::move(stream), prefixLength));
return Status::OK();
if (status.ok()) {
if (stream) {
result->reset(new EncryptedRandomRWFile(
std::move(underlying), std::move(stream), prefix_length));
} else {
result->reset(underlying.release());
}
}
return status;
}
// Store in *result the attributes of the children of the specified
@ -671,14 +809,19 @@ class EncryptedEnv : public EnvWrapper {
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);
// 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;
EncryptionProvider* provider;
status = GetReadableProvider(it->name, &provider);
if (!status.ok()) {
return status;
} else if (provider != nullptr) {
it->size_bytes -= provider->GetPrefixLength();
}
}
return Status::OK();
}
@ -690,20 +833,25 @@ class EncryptedEnv : public EnvWrapper {
if (!status.ok()) {
return status;
}
size_t prefixLength = provider_->GetPrefixLength();
assert(*file_size >= prefixLength);
*file_size -= prefixLength;
return Status::OK();
EncryptionProvider* provider;
status = GetReadableProvider(fname, &provider);
if (provider != nullptr && status.ok()) {
size_t prefixLength = provider->GetPrefixLength();
assert(*file_size >= prefixLength);
*file_size -= prefixLength;
}
return status;
}
private:
EncryptionProvider* provider_;
std::shared_ptr<EncryptionProvider> provider_;
};
// 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);
Env* NewEncryptedEnv(Env* base_env,
const std::shared_ptr<EncryptionProvider>& provider) {
return new EncryptedEnvImpl(base_env, provider);
}
// Encrypt one or more (partial) blocks of data at the file offset.
@ -804,38 +952,38 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char *data, size_t
}
}
const char* ROT13BlockCipher::Name() const { return kROT13CipherName; }
// Encrypt a block of data.
// Length of data is equal to BlockSize().
Status ROT13BlockCipher::Encrypt(char *data) {
Status ROT13BlockCipher::Encrypt(char* data) {
for (size_t i = 0; i < blockSize_; ++i) {
data[i] += 13;
data[i] += 13;
}
return Status::OK();
}
// Decrypt a block of data.
// Length of data is equal to BlockSize().
Status ROT13BlockCipher::Decrypt(char *data) {
return Encrypt(data);
}
Status ROT13BlockCipher::Decrypt(char* data) { return Encrypt(data); }
// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
void CTRCipherStream::AllocateScratch(std::string& scratch) {
auto blockSize = cipher_.BlockSize();
auto blockSize = cipher_->BlockSize();
scratch.reserve(blockSize);
}
// Encrypt a block of data at the given block index.
// Length of data is equal to BlockSize();
Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char *data, char* scratch) {
Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char* data,
char* scratch) {
// Create nonce + counter
auto blockSize = cipher_.BlockSize();
auto blockSize = cipher_->BlockSize();
memmove(scratch, iv_.data(), blockSize);
EncodeFixed64(scratch, blockIndex + initialCounter_);
// Encrypt nonce+counter
auto status = cipher_.Encrypt(scratch);
auto status = cipher_->Encrypt(scratch);
if (!status.ok()) {
return status;
}
@ -849,22 +997,48 @@ 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) {
Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char* data,
char* scratch) {
// For CTR decryption & encryption are the same
return EncryptBlock(blockIndex, data, scratch);
}
const char* CTREncryptionProvider::Name() const { return kCTRProviderName; }
// 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.
size_t CTREncryptionProvider::GetPrefixLength() {
size_t CTREncryptionProvider::GetPrefixLength() const {
return defaultPrefixLength;
}
Status CTREncryptionProvider::TEST_Initialize() {
if (!cipher_) {
return BlockCipher::CreateFromString(
ConfigOptions(), std::string(kROT13CipherName) + ":32", &cipher_);
}
return Status::OK();
}
Status CTREncryptionProvider::AddCipher(const std::string& /*descriptor*/,
const char* cipher, size_t len,
bool /*for_write*/) {
if (cipher_) {
return Status::NotSupported("Cannot add keys to CTREncryptionProvider");
} else if (strcmp(kROT13CipherName, cipher) == 0) {
cipher_.reset(new ROT13BlockCipher(len));
return Status::OK();
} else {
return BlockCipher::CreateFromString(ConfigOptions(), std::string(cipher),
&cipher_);
}
}
// decodeCTRParameters decodes the initial counter & IV from the given
// (plain text) prefix.
static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t &initialCounter, Slice &iv) {
static void decodeCTRParameters(const char* prefix, size_t blockSize,
uint64_t& initialCounter, Slice& iv) {
// First block contains 64-bit initial counter
initialCounter = DecodeFixed64(prefix);
// Second block contains IV
@ -875,7 +1049,10 @@ static void decodeCTRParameters(const char *prefix, size_t blockSize, uint64_t &
// for a new file.
Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
char* prefix,
size_t prefixLength) {
size_t prefixLength) const {
if (!cipher_) {
return Status::InvalidArgument("Encryption Cipher is missing");
}
// Create & seed rnd.
Random rnd((uint32_t)Env::Default()->NowMicros());
// Fill entire prefix block with random values.
@ -883,13 +1060,14 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
prefix[i] = rnd.Uniform(256) & 0xFF;
}
// Take random data to extract initial counter & IV
auto blockSize = cipher_.BlockSize();
auto blockSize = cipher_->BlockSize();
uint64_t initialCounter;
Slice prefixIV;
decodeCTRParameters(prefix, blockSize, initialCounter, prefixIV);
// Now populate the rest of the prefix, starting from the third block.
PopulateSecretPrefixPart(prefix + (2 * blockSize), prefixLength - (2 * blockSize), blockSize);
PopulateSecretPrefixPart(prefix + (2 * blockSize),
prefixLength - (2 * blockSize), blockSize);
// Encrypt the prefix, starting from block 2 (leave block 0, 1 with initial
// counter & IV unencrypted)
@ -910,9 +1088,8 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
// in plain text.
// Returns the amount of space (starting from the start of the prefix)
// that has been initialized.
size_t CTREncryptionProvider::PopulateSecretPrefixPart(char* /*prefix*/,
size_t /*prefixLength*/,
size_t /*blockSize*/) {
size_t CTREncryptionProvider::PopulateSecretPrefixPart(
char* /*prefix*/, size_t /*prefixLength*/, size_t /*blockSize*/) const {
// Nothing to do here, put in custom data in override when needed.
return 0;
}
@ -920,8 +1097,11 @@ size_t CTREncryptionProvider::PopulateSecretPrefixPart(char* /*prefix*/,
Status CTREncryptionProvider::CreateCipherStream(
const std::string& fname, const EnvOptions& options, Slice& prefix,
std::unique_ptr<BlockAccessCipherStream>* result) {
if (!cipher_) {
return Status::InvalidArgument("Encryption Cipher is missing");
}
// Read plain text part of prefix.
auto blockSize = cipher_.BlockSize();
auto blockSize = cipher_->BlockSize();
uint64_t initialCounter;
Slice iv;
decodeCTRParameters(prefix.data(), blockSize, initialCounter, iv);
@ -948,11 +1128,12 @@ Status CTREncryptionProvider::CreateCipherStream(
}
// Create cipher stream
return CreateCipherStreamFromPrefix(fname, options, initialCounter, iv, prefix, result);
return CreateCipherStreamFromPrefix(fname, options, initialCounter, iv,
prefix, result);
}
// CreateCipherStreamFromPrefix creates a block access cipher stream for a file given
// given name and options. The given prefix is already decrypted.
// CreateCipherStreamFromPrefix creates a block access cipher stream for a file
// given given name and options. The given prefix is already decrypted.
Status CTREncryptionProvider::CreateCipherStreamFromPrefix(
const std::string& /*fname*/, const EnvOptions& /*options*/,
uint64_t initialCounter, const Slice& iv, const Slice& /*prefix*/,

137
env/env_encryption_ctr.h vendored Normal file
View File

@ -0,0 +1,137 @@
// Copyright (c) 2016-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
#if !defined(ROCKSDB_LITE)
#include "rocksdb/env_encryption.h"
namespace ROCKSDB_NAMESPACE {
// Implements a BlockCipher using ROT13.
//
// 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(){};
const char* Name() const override;
// BlockSize returns the size of each block supported by this cipher stream.
size_t BlockSize() override { return blockSize_; }
// Encrypt a block of data.
// Length of data is equal to BlockSize().
Status Encrypt(char* data) override;
// Decrypt a block of data.
// Length of data is equal to BlockSize().
Status Decrypt(char* data) override;
};
// 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,
// it is considered suitable for use.
class CTRCipherStream final : public BlockAccessCipherStream {
private:
std::shared_ptr<BlockCipher> cipher_;
std::string iv_;
uint64_t initialCounter_;
public:
CTRCipherStream(const std::shared_ptr<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.
size_t BlockSize() override { return cipher_->BlockSize(); }
protected:
// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
void AllocateScratch(std::string&) override;
// Encrypt a block of data at the given block index.
// Length of data is equal to BlockSize();
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();
Status DecryptBlock(uint64_t blockIndex, char* data, char* scratch) override;
};
// This encryption provider uses a CTR cipher stream, with a given block cipher
// and IV.
//
// 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:
std::shared_ptr<BlockCipher> cipher_;
protected:
// For optimal performance when using direct IO, the prefix length should be a
// multiple of the page size. This size is to ensure the first real data byte
// is placed at largest known alignment point for direct io.
const static size_t defaultPrefixLength = 4096;
public:
explicit CTREncryptionProvider(
const std::shared_ptr<BlockCipher>& c = nullptr)
: cipher_(c){};
virtual ~CTREncryptionProvider() {}
const char* Name() const override;
// GetPrefixLength returns the length of the prefix that is added to every
// file
// and used for storing encryption options.
// For optimal performance when using direct IO, the prefix length should be a
// multiple of the page size.
size_t GetPrefixLength() const override;
// CreateNewPrefix initialized an allocated block of prefix memory
// for a new file.
Status CreateNewPrefix(const std::string& fname, char* prefix,
size_t prefixLength) const override;
// CreateCipherStream creates a block access cipher stream for a file given
// given name and options.
Status CreateCipherStream(
const std::string& fname, const EnvOptions& options, Slice& prefix,
std::unique_ptr<BlockAccessCipherStream>* result) override;
Status AddCipher(const std::string& descriptor, const char* /*cipher*/,
size_t /*len*/, bool /*for_write*/) override;
protected:
Status TEST_Initialize() override;
// 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) const;
// 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<BlockAccessCipherStream>* result);
};
} // namespace ROCKSDB_NAMESPACE
#endif // !defined(ROCKSDB_LITE)

View File

@ -9,15 +9,19 @@
#include <string>
#include "env.h"
#include "rocksdb/env.h"
#include "rocksdb/rocksdb_namespace.h"
namespace ROCKSDB_NAMESPACE {
class EncryptionProvider;
struct ConfigOptions;
// Returns an Env that encrypts data when stored on disk and decrypts data when
// read from disk.
Env* NewEncryptedEnv(Env* base_env, EncryptionProvider* provider);
Env* NewEncryptedEnv(Env* base_env,
const std::shared_ptr<EncryptionProvider>& provider);
// BlockAccessCipherStream is the base class for any cipher stream that
// supports random access at block level (without requiring data from other
@ -57,6 +61,30 @@ class BlockCipher {
public:
virtual ~BlockCipher(){};
// Creates a new BlockCipher from the input config_options and value
// The value describes the type of provider (and potentially optional
// configuration parameters) used to create this provider.
// For example, if the value is "ROT13", a ROT13BlockCipher is created.
//
// @param config_options Options to control how this cipher is created
// and initialized.
// @param value The value might be:
// - ROT13 Create a ROT13 Cipher
// - ROT13:nn Create a ROT13 Cipher with block size of nn
// @param result The new cipher object
// @return OK if the cipher was sucessfully created
// @return NotFound if an invalid name was specified in the value
// @return InvalidArgument if either the options were not valid
static Status CreateFromString(const ConfigOptions& config_options,
const std::string& value,
std::shared_ptr<BlockCipher>* result);
// Short-cut method to create a ROT13 BlockCipher.
// This cipher is only suitable for test purposes and should not be used in
// production!!!
static std::shared_ptr<BlockCipher> NewROT13Cipher(size_t block_size);
virtual const char* Name() const = 0;
// BlockSize returns the size of each block supported by this cipher stream.
virtual size_t BlockSize() = 0;
@ -69,65 +97,6 @@ class BlockCipher {
virtual Status Decrypt(char* data) = 0;
};
// Implements a BlockCipher using ROT13.
//
// 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;
};
// 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,
// 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;
};
// 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.
@ -135,73 +104,71 @@ class EncryptionProvider {
public:
virtual ~EncryptionProvider(){};
// Creates a new EncryptionProvider from the input config_options and value
// The value describes the type of provider (and potentially optional
// configuration parameters) used to create this provider.
// For example, if the value is "CTR", a CTREncryptionProvider will be
// created. If the value is preceded by "test://" (e.g test://CTR"), the
// TEST_Initialize method will be invoked prior to returning the provider.
//
// @param config_options Options to control how this provider is created
// and initialized.
// @param value The value might be:
// - CTR Create a CTR provider
// - test://CTR Create a CTR provider and initialize it for tests.
// @param result The new provider object
// @return OK if the provider was sucessfully created
// @return NotFound if an invalid name was specified in the value
// @return InvalidArgument if either the options were not valid
static Status CreateFromString(const ConfigOptions& config_options,
const std::string& value,
std::shared_ptr<EncryptionProvider>* result);
// Short-cut method to create a CTR-provider
static std::shared_ptr<EncryptionProvider> NewCTRProvider(
const std::shared_ptr<BlockCipher>& cipher);
// Returns the name of this EncryptionProvider
virtual const char* Name() const = 0;
// 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;
virtual size_t GetPrefixLength() const = 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;
size_t prefixLength) const = 0;
// Method to add a new cipher key for use by the EncryptionProvider.
// @param description Descriptor for this key.
// @param cipher The cryptographic key to use
// @param len The length of the cipher key
// @param for_write If true, this cipher should be used for writing files.
// If false, this cipher should only be used for reading
// files
// @return OK if the cipher was successfully added to the provider, non-OK
// otherwise
virtual Status AddCipher(const std::string& descriptor, const char* cipher,
size_t len, bool for_write) = 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<BlockAccessCipherStream>* result) = 0;
};
// This encryption provider uses a CTR cipher stream, with a given block cipher
// and IV.
//
// 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_;
// Returns a string representing an encryption marker prefix for this
// provider. If a marker is provided, this marker can be used to tell whether
// or not a file is encrypted by this provider. The maker will also be part
// of any encryption prefix for this provider.
virtual std::string GetMarker() const { return ""; }
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<BlockAccessCipherStream>* 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<BlockAccessCipherStream>* result);
// Optional method to initialize an EncryptionProvider in the TEST
// environment.
virtual Status TEST_Initialize() { return Status::OK(); }
};
class EncryptedSequentialFile : public SequentialFile {