Merge branch 'main' into vv
This commit is contained in:
commit
c106847e3e
|
@ -619,23 +619,23 @@ FDBFuture* fdb_transaction_get_range_impl(FDBTransaction* tr,
|
|||
.extractPtr());
|
||||
}
|
||||
|
||||
FDBFuture* fdb_transaction_get_mapped_range_impl(FDBTransaction* tr,
|
||||
uint8_t const* begin_key_name,
|
||||
int begin_key_name_length,
|
||||
fdb_bool_t begin_or_equal,
|
||||
int begin_offset,
|
||||
uint8_t const* end_key_name,
|
||||
int end_key_name_length,
|
||||
fdb_bool_t end_or_equal,
|
||||
int end_offset,
|
||||
uint8_t const* mapper_name,
|
||||
int mapper_name_length,
|
||||
int limit,
|
||||
int target_bytes,
|
||||
FDBStreamingMode mode,
|
||||
int iteration,
|
||||
fdb_bool_t snapshot,
|
||||
fdb_bool_t reverse) {
|
||||
extern "C" DLLEXPORT FDBFuture* fdb_transaction_get_mapped_range(FDBTransaction* tr,
|
||||
uint8_t const* begin_key_name,
|
||||
int begin_key_name_length,
|
||||
fdb_bool_t begin_or_equal,
|
||||
int begin_offset,
|
||||
uint8_t const* end_key_name,
|
||||
int end_key_name_length,
|
||||
fdb_bool_t end_or_equal,
|
||||
int end_offset,
|
||||
uint8_t const* mapper_name,
|
||||
int mapper_name_length,
|
||||
int limit,
|
||||
int target_bytes,
|
||||
FDBStreamingMode mode,
|
||||
int iteration,
|
||||
fdb_bool_t snapshot,
|
||||
fdb_bool_t reverse) {
|
||||
FDBFuture* r = validate_and_update_parameters(limit, target_bytes, mode, iteration, reverse);
|
||||
if (r != nullptr)
|
||||
return r;
|
||||
|
@ -651,25 +651,24 @@ FDBFuture* fdb_transaction_get_mapped_range_impl(FDBTransaction* tr,
|
|||
.extractPtr());
|
||||
}
|
||||
|
||||
// TODO: Support FDB_API_ADDED in generate_asm.py and then this can be replaced with fdb_api_ptr_unimpl.
|
||||
FDBFuture* fdb_transaction_get_mapped_range_v699(FDBTransaction* tr,
|
||||
uint8_t const* begin_key_name,
|
||||
int begin_key_name_length,
|
||||
fdb_bool_t begin_or_equal,
|
||||
int begin_offset,
|
||||
uint8_t const* end_key_name,
|
||||
int end_key_name_length,
|
||||
fdb_bool_t end_or_equal,
|
||||
int end_offset,
|
||||
uint8_t const* mapper_name,
|
||||
int mapper_name_length,
|
||||
int limit,
|
||||
int target_bytes,
|
||||
FDBStreamingMode mode,
|
||||
int iteration,
|
||||
fdb_bool_t snapshot,
|
||||
fdb_bool_t reverse) {
|
||||
fprintf(stderr, "UNIMPLEMENTED FDB API FUNCTION\n");
|
||||
FDBFuture* fdb_transaction_get_range_and_flat_map_v709(FDBTransaction* tr,
|
||||
uint8_t const* begin_key_name,
|
||||
int begin_key_name_length,
|
||||
fdb_bool_t begin_or_equal,
|
||||
int begin_offset,
|
||||
uint8_t const* end_key_name,
|
||||
int end_key_name_length,
|
||||
fdb_bool_t end_or_equal,
|
||||
int end_offset,
|
||||
uint8_t const* mapper_name,
|
||||
int mapper_name_length,
|
||||
int limit,
|
||||
int target_bytes,
|
||||
FDBStreamingMode mode,
|
||||
int iteration,
|
||||
fdb_bool_t snapshot,
|
||||
fdb_bool_t reverse) {
|
||||
fprintf(stderr, "GetRangeAndFlatMap is removed from 7.0. Please upgrade to 7.1 and use GetMappedRange\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
|
@ -900,13 +899,13 @@ extern "C" DLLEXPORT fdb_error_t fdb_select_api_version_impl(int runtime_version
|
|||
|
||||
// Versioned API changes -- descending order by version (new changes at top)
|
||||
// FDB_API_CHANGED( function, ver ) means there is a new implementation as of ver, and a function function_(ver-1)
|
||||
// is the old implementation FDB_API_REMOVED( function, ver ) means the function was removed as of ver, and
|
||||
// is the old implementation. FDB_API_REMOVED( function, ver ) means the function was removed as of ver, and
|
||||
// function_(ver-1) is the old implementation
|
||||
//
|
||||
// WARNING: use caution when implementing removed functions by calling public API functions. This can lead to
|
||||
// undesired behavior when using the multi-version API. Instead, it is better to have both the removed and public
|
||||
// functions call an internal implementation function. See fdb_create_database_impl for an example.
|
||||
FDB_API_CHANGED(fdb_transaction_get_mapped_range, 700);
|
||||
FDB_API_REMOVED(fdb_transaction_get_range_and_flat_map, 710);
|
||||
FDB_API_REMOVED(fdb_future_get_version, 620);
|
||||
FDB_API_REMOVED(fdb_create_cluster, 610);
|
||||
FDB_API_REMOVED(fdb_cluster_create_database, 610);
|
||||
|
|
|
@ -28,7 +28,6 @@ Features
|
|||
* Improved the efficiency with which storage servers replicate data between themselves. `(PR #5017) <https://github.com/apple/foundationdb/pull/5017>`_
|
||||
* Added support to ``exclude command`` to exclude based on locality match. `(PR #5113) <https://github.com/apple/foundationdb/pull/5113>`_
|
||||
* Add the ``trace_partial_file_suffix`` network option. This option will give unfinished trace files a special suffix to indicate they're not complete yet. When the trace file is complete, it is renamed to remove the suffix. `(PR #5328) <https://github.com/apple/foundationdb/pull/5328>`_
|
||||
* Added "get range and flat map" feature with new APIs (see Bindings section). Storage servers are able to generate the keys in the queries based on another query. With this, upper layer can push some computations down to FDB, to improve latency and bandwidth when read. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
@ -85,8 +84,6 @@ Bindings
|
|||
* C: Added a function, ``fdb_database_create_snapshot``, to create a snapshot of the database. `(PR #4241) <https://github.com/apple/foundationdb/pull/4241/files>`_
|
||||
* C: Added ``fdb_database_get_main_thread_busyness`` function to report how busy a client's main thread is. `(PR #4504) <https://github.com/apple/foundationdb/pull/4504>`_
|
||||
* Java: Added ``Database.getMainThreadBusyness`` function to report how busy a client's main thread is. `(PR #4564) <https://github.com/apple/foundationdb/pull/4564>`_
|
||||
* C: Added ``fdb_transaction_get_range_and_flat_map`` function to support running queries based on another query in one request. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_
|
||||
* Java: Added ``Transaction.getRangeAndFlatMap`` function to support running queries based on another query in one request. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
|
|
|
@ -10,6 +10,7 @@ Release Notes
|
|||
Features
|
||||
--------
|
||||
* Added ``USE_GRV_CACHE`` transaction option to allow read versions to be locally cached on the client side for latency optimizations. `(PR #5725) <https://github.com/apple/foundationdb/pull/5725>`_ `(PR #6664) <https://github.com/apple/foundationdb/pull/6664>`_
|
||||
* Added "get range and flat map" feature with new APIs (see Bindings section). Storage servers are able to generate the keys in the queries based on another query. With this, upper layer can push some computations down to FDB, to improve latency and bandwidth when read. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_, `(PR #6181) <https://github.com/apple/foundationdb/pull/6181>`_, etc..
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
@ -25,6 +26,8 @@ Status
|
|||
|
||||
Bindings
|
||||
--------
|
||||
* C: Added ``fdb_transaction_get_range_and_flat_map`` function to support running queries based on another query in one request. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_
|
||||
* Java: Added ``Transaction.getRangeAndFlatMap`` function to support running queries based on another query in one request. `(PR #5609) <https://github.com/apple/foundationdb/pull/5609>`_
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
|
|
|
@ -614,7 +614,7 @@ void DLApi::init() {
|
|||
headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetRange, lib, fdbCPath, "fdb_transaction_get_range", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->transactionGetMappedRange, lib, fdbCPath, "fdb_transaction_get_mapped_range", headerVersion >= 700);
|
||||
&api->transactionGetMappedRange, lib, fdbCPath, "fdb_transaction_get_mapped_range", headerVersion >= 710);
|
||||
loadClientFunction(
|
||||
&api->transactionGetVersionstamp, lib, fdbCPath, "fdb_transaction_get_versionstamp", headerVersion >= 410);
|
||||
loadClientFunction(&api->transactionSet, lib, fdbCPath, "fdb_transaction_set", headerVersion >= 0);
|
||||
|
@ -672,7 +672,7 @@ void DLApi::init() {
|
|||
loadClientFunction(
|
||||
&api->futureGetKeyValueArray, lib, fdbCPath, "fdb_future_get_keyvalue_array", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->futureGetMappedKeyValueArray, lib, fdbCPath, "fdb_future_get_mappedkeyvalue_array", headerVersion >= 700);
|
||||
&api->futureGetMappedKeyValueArray, lib, fdbCPath, "fdb_future_get_mappedkeyvalue_array", headerVersion >= 710);
|
||||
loadClientFunction(&api->futureGetSharedState, lib, fdbCPath, "fdb_future_get_shared_state", headerVersion >= 710);
|
||||
loadClientFunction(&api->futureSetCallback, lib, fdbCPath, "fdb_future_set_callback", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureCancel, lib, fdbCPath, "fdb_future_cancel", headerVersion >= 0);
|
||||
|
|
|
@ -835,6 +835,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( REDWOOD_METRICS_INTERVAL, 5.0 );
|
||||
init( REDWOOD_HISTOGRAM_INTERVAL, 30.0 );
|
||||
init( REDWOOD_EVICT_UPDATED_PAGES, true ); if( randomize && BUGGIFY ) { REDWOOD_EVICT_UPDATED_PAGES = false; }
|
||||
init( REDWOOD_DECODECACHE_REUSE_MIN_HEIGHT, 2 ); if( randomize && BUGGIFY ) { REDWOOD_DECODECACHE_REUSE_MIN_HEIGHT = deterministicRandom()->randomInt(1, 7); }
|
||||
|
||||
// Server request latency measurement
|
||||
init( LATENCY_SAMPLE_SIZE, 100000 );
|
||||
|
|
|
@ -798,6 +798,7 @@ public:
|
|||
double REDWOOD_METRICS_INTERVAL;
|
||||
double REDWOOD_HISTOGRAM_INTERVAL;
|
||||
bool REDWOOD_EVICT_UPDATED_PAGES; // Whether to prioritize eviction of updated pages from cache.
|
||||
int REDWOOD_DECODECACHE_REUSE_MIN_HEIGHT; // Minimum height for which to keep and reuse page decode caches
|
||||
|
||||
// Server request latency measurement
|
||||
int LATENCY_SAMPLE_SIZE;
|
||||
|
|
|
@ -1177,15 +1177,16 @@ public:
|
|||
struct Cursor {
|
||||
Cursor() : cache(nullptr), nodeIndex(-1) {}
|
||||
|
||||
Cursor(DecodeCache* cache, DeltaTree2* tree) : tree(tree), cache(cache), nodeIndex(-1) {}
|
||||
Cursor(Reference<DecodeCache> cache, DeltaTree2* tree) : tree(tree), cache(cache), nodeIndex(-1) {}
|
||||
|
||||
Cursor(DecodeCache* cache, DeltaTree2* tree, int nodeIndex) : tree(tree), cache(cache), nodeIndex(nodeIndex) {}
|
||||
Cursor(Reference<DecodeCache> cache, DeltaTree2* tree, int nodeIndex)
|
||||
: tree(tree), cache(cache), nodeIndex(nodeIndex) {}
|
||||
|
||||
// Copy constructor does not copy item because normally a copied cursor will be immediately moved.
|
||||
Cursor(const Cursor& c) : tree(c.tree), cache(c.cache), nodeIndex(c.nodeIndex) {}
|
||||
|
||||
~Cursor() {
|
||||
if (cache != nullptr) {
|
||||
if (cache.isValid()) {
|
||||
cache->updateUsedMemory();
|
||||
}
|
||||
}
|
||||
|
@ -1212,7 +1213,7 @@ public:
|
|||
}
|
||||
|
||||
DeltaTree2* tree;
|
||||
DecodeCache* cache;
|
||||
Reference<DecodeCache> cache;
|
||||
int nodeIndex;
|
||||
mutable Optional<T> item;
|
||||
|
||||
|
@ -1274,6 +1275,7 @@ public:
|
|||
return item.get();
|
||||
}
|
||||
|
||||
// Switch the cursor to point to a new DeltaTree
|
||||
void switchTree(DeltaTree2* newTree) {
|
||||
tree = newTree;
|
||||
// Reset item because it may point into tree memory
|
||||
|
@ -1709,7 +1711,13 @@ public:
|
|||
} else {
|
||||
nodeBytesUsed = 0;
|
||||
}
|
||||
|
||||
ASSERT(size() <= spaceAvailable);
|
||||
nodeBytesFree = spaceAvailable - size();
|
||||
|
||||
// Zero unused available space
|
||||
memset((uint8_t*)this + size(), 0, nodeBytesFree);
|
||||
|
||||
return size();
|
||||
}
|
||||
|
||||
|
@ -1782,8 +1790,15 @@ private:
|
|||
node.setLeftChildOffset(largeNodes, leftChildOffset);
|
||||
node.setRightChildOffset(largeNodes, rightChildOffset);
|
||||
|
||||
deltatree_printf("%p: Serialized %s as %s\n", this, item.toString().c_str(), node.toString(this).c_str());
|
||||
int written = wptr - (uint8_t*)&node;
|
||||
deltatree_printf("Built subtree tree=%p subtreeRoot=%p written=%d end=%p serialized subtreeRoot %s as %s \n",
|
||||
this,
|
||||
&node,
|
||||
written,
|
||||
(uint8_t*)&node + written,
|
||||
item.toString().c_str(),
|
||||
node.toString(this).c_str());
|
||||
|
||||
return wptr - (uint8_t*)&node;
|
||||
return written;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,22 +20,24 @@
|
|||
|
||||
#ifndef FDBSERVER_IPAGER_H
|
||||
#define FDBSERVER_IPAGER_H
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastAlloc.h"
|
||||
#include "flow/ProtocolVersion.h"
|
||||
#include <cstddef>
|
||||
#include <stdint.h>
|
||||
#pragma once
|
||||
|
||||
#include "fdbserver/IKeyValueStore.h"
|
||||
|
||||
#include "flow/flow.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#define XXH_INLINE_ALL
|
||||
#include "flow/xxhash.h"
|
||||
|
||||
#ifndef VALGRIND
|
||||
#define VALGRIND_MAKE_MEM_UNDEFINED(x, y)
|
||||
#define VALGRIND_MAKE_MEM_DEFINED(x, y)
|
||||
#endif
|
||||
|
||||
typedef uint32_t LogicalPageID;
|
||||
typedef uint32_t PhysicalPageID;
|
||||
#define invalidLogicalPageID std::numeric_limits<LogicalPageID>::max()
|
||||
#define invalidPhysicalPageID std::numeric_limits<PhysicalPageID>::max()
|
||||
|
||||
typedef uint32_t QueueID;
|
||||
#define invalidQueueID std::numeric_limits<QueueID>::max()
|
||||
|
@ -76,90 +78,509 @@ static const std::vector<std::pair<PagerEvents, PagerEventReasons>> L0PossibleEv
|
|||
{ PagerEvents::PageWrite, PagerEventReasons::MetaData },
|
||||
};
|
||||
|
||||
// Represents a block of memory in a 4096-byte aligned location held by an Arena.
|
||||
enum EncodingType : uint8_t {
|
||||
XXHash64 = 0,
|
||||
// For testing purposes
|
||||
XOREncryption = 1
|
||||
};
|
||||
|
||||
enum PageType : uint8_t {
|
||||
HeaderPage = 0,
|
||||
BackupHeaderPage = 1,
|
||||
BTreeNode = 2,
|
||||
BTreeSuperNode = 3,
|
||||
QueuePageStandalone = 4,
|
||||
QueuePageInExtent = 5
|
||||
};
|
||||
|
||||
// Encryption key ID
|
||||
typedef uint64_t KeyID;
|
||||
|
||||
// EncryptionKeyRef is somewhat multi-variant, it will contain members representing the union
|
||||
// of all fields relevant to any implemented encryption scheme. They are generally of
|
||||
// the form
|
||||
// Page Fields - fields which come from or are stored in the Page
|
||||
// Secret Fields - fields which are only known by the Key Provider
|
||||
// but it is up to each encoding and provider which fields are which and which ones are used
|
||||
struct EncryptionKeyRef {
|
||||
|
||||
EncryptionKeyRef(){};
|
||||
EncryptionKeyRef(Arena& arena, const EncryptionKeyRef& toCopy) : secret(arena, toCopy.secret), id(toCopy.id) {}
|
||||
int expectedSize() const { return secret.size(); }
|
||||
|
||||
StringRef secret;
|
||||
Optional<KeyID> id;
|
||||
};
|
||||
typedef Standalone<EncryptionKeyRef> EncryptionKey;
|
||||
|
||||
// Interface used by pager to get encryption keys by ID when reading pages from disk
|
||||
// and by the BTree to get encryption keys to use for new pages
|
||||
class IEncryptionKeyProvider {
|
||||
public:
|
||||
virtual ~IEncryptionKeyProvider() {}
|
||||
|
||||
// Get an EncryptionKey with Secret Fields populated based on the given Page Fields.
|
||||
// It is up to the implementation which fields those are.
|
||||
// The output Page Fields must match the input Page Fields.
|
||||
virtual Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) = 0;
|
||||
|
||||
// Get encryption key that should be used for a given user Key-Value range
|
||||
virtual Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) = 0;
|
||||
};
|
||||
|
||||
// This is a hacky way to attach an additional object of an arbitrary type at runtime to another object.
|
||||
// It stores an arbitrary void pointer and a void pointer function to call when the ArbitraryObject
|
||||
// is destroyed.
|
||||
// It has helper operator= methods for storing heap-allocated T's or Reference<T>'s in into it via
|
||||
// x = thing;
|
||||
// Examples:
|
||||
// ArbitraryObject x;
|
||||
// x.set(new Widget()); // x owns the new object
|
||||
// x.set(Reference<SomeClass>(new SomeClass()); // x holds a reference now too
|
||||
// x.setReference(new SomeReferenceCountedType()); //
|
||||
struct ArbitraryObject {
|
||||
ArbitraryObject() : ptr(nullptr), onDestruct(nullptr) {}
|
||||
ArbitraryObject(const ArbitraryObject&) = delete;
|
||||
|
||||
~ArbitraryObject() { destructOnly(); }
|
||||
|
||||
bool valid() const { return ptr != nullptr; }
|
||||
|
||||
template <typename T>
|
||||
void operator=(T* p) {
|
||||
destructOnly();
|
||||
ptr = p;
|
||||
onDestruct = [](void* ptr) { delete (T*)ptr; };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator=(Reference<T>& r) {
|
||||
destructOnly();
|
||||
ptr = r.getPtr();
|
||||
r.getPtr()->addref();
|
||||
onDestruct = [](void* ptr) { ((T*)ptr)->delref(); };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator=(Reference<T>&& r) {
|
||||
destructOnly();
|
||||
ptr = r.extractPtr();
|
||||
onDestruct = [](void* ptr) { ((T*)ptr)->delref(); };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* getPtr() {
|
||||
return (T*)ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Reference<T> getReference() {
|
||||
return Reference<T>::addRef((T*)ptr);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
destructOnly();
|
||||
ptr = nullptr;
|
||||
onDestruct = nullptr;
|
||||
}
|
||||
|
||||
// ptr can be set to any arbitrary thing. If it is not null at destruct time then
|
||||
// onDestruct(ptr) will be called if onDestruct is not null.
|
||||
void* ptr = nullptr;
|
||||
void (*onDestruct)(void*) = nullptr;
|
||||
|
||||
private:
|
||||
// Call onDestruct(ptr) if needed but don't reset any state
|
||||
void destructOnly() {
|
||||
if (ptr != nullptr && onDestruct != nullptr) {
|
||||
onDestruct(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ArenaPage represents a data page meant to be stored on disk, located in a block of
|
||||
// 4k-aligned memory held by an Arena
|
||||
//
|
||||
// Page Format:
|
||||
// PageHeader - describes main header version, encoding type, and offsets of subheaders and payload.
|
||||
// MainHeader - structure based on header version. It is responsible for protecting all bytes
|
||||
// of PageHeader, MainHeader, and EncodingHeader with some sort of checksum.
|
||||
// EncodingHeader - structure based on encoding type. It is responsible for protecting and
|
||||
// possibly encrypting all payload bytes.
|
||||
// Payload - User accessible bytes, protected and possibly encrypted based on the encoding
|
||||
//
|
||||
// preWrite() must be called before writing a page to disk to update checksums and encrypt as needed
|
||||
// After reading a page from disk,
|
||||
// postReadHeader() must be called to verify the verison, main, and encoding headers
|
||||
// postReadPayload() must be called, after potentially setting encryption secret, to verify and possibly
|
||||
// decrypt the payload
|
||||
class ArenaPage : public ReferenceCounted<ArenaPage>, public FastAllocated<ArenaPage> {
|
||||
public:
|
||||
// The page's logical size includes an opaque checksum, use size() to get usable size
|
||||
ArenaPage(int logicalSize, int bufferSize) : logicalSize(logicalSize), bufferSize(bufferSize), userData(nullptr) {
|
||||
// This is the header version that new page init() calls will use.
|
||||
// It is not necessarily the latest header version, as read/modify support for
|
||||
// a new header version may be added prior to using that version as the default
|
||||
// for new pages as part of downgrade support.
|
||||
static constexpr uint8_t HEADER_WRITE_VERSION = 1;
|
||||
|
||||
ArenaPage(int logicalSize, int bufferSize) : logicalSize(logicalSize), bufferSize(bufferSize), pPayload(nullptr) {
|
||||
if (bufferSize > 0) {
|
||||
buffer = (uint8_t*)arena.allocate4kAlignedBuffer(bufferSize);
|
||||
|
||||
// Mark any unused page portion defined
|
||||
VALGRIND_MAKE_MEM_DEFINED(buffer + logicalSize, bufferSize - logicalSize);
|
||||
// Zero unused region
|
||||
memset(buffer + logicalSize, 0, bufferSize - logicalSize);
|
||||
} else {
|
||||
buffer = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
~ArenaPage() {
|
||||
if (userData != nullptr && userDataDestructor != nullptr) {
|
||||
userDataDestructor(userData);
|
||||
~ArenaPage() {}
|
||||
|
||||
// Before using these, either init() or postReadHeader and postReadPayload() must be called
|
||||
const uint8_t* data() const { return pPayload; }
|
||||
uint8_t* mutateData() const { return (uint8_t*)pPayload; }
|
||||
int dataSize() const { return payloadSize; }
|
||||
|
||||
StringRef dataAsStringRef() const { return StringRef((uint8_t*)pPayload, payloadSize); }
|
||||
|
||||
const uint8_t* rawData() const { return buffer; }
|
||||
uint8_t* rawData() { return buffer; }
|
||||
int rawSize() const { return bufferSize; }
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// The next few structs describe the byte-packed physical structure. The fields of Page
|
||||
// cannot change, but new header versions and encoding types can be added and existing
|
||||
// header versions and encoding type headers could change size as offset information
|
||||
// is stored to enable efficient jumping to the encoding header or payload.
|
||||
// Page members are only initialized in init()
|
||||
struct PageHeader {
|
||||
uint8_t headerVersion;
|
||||
EncodingType encodingType;
|
||||
|
||||
// Encoding header comes after main header
|
||||
uint8_t encodingHeaderOffset;
|
||||
|
||||
// Payload comes after encoding header
|
||||
uint8_t payloadOffset;
|
||||
|
||||
// Get main header pointer, casting to its type
|
||||
template <typename T>
|
||||
T* getMainHeader() const {
|
||||
return (T*)(this + 1);
|
||||
}
|
||||
|
||||
// Get encoding header pointer, casting to its type
|
||||
template <typename T>
|
||||
T* getEncodingHeader() const {
|
||||
return (T*)((uint8_t*)this + encodingHeaderOffset);
|
||||
}
|
||||
|
||||
// Get payload pointer
|
||||
uint8_t* getPayload() const { return (uint8_t*)this + payloadOffset; }
|
||||
};
|
||||
|
||||
// Redwood header version 1
|
||||
// Protects all headers with a 64-bit XXHash checksum
|
||||
// Most other fields are forensic in nature and are not required to be set for correct
|
||||
// behavior but they can faciliate forensic investigation of data on disk. Some of them
|
||||
// could be used for sanity checks at runtime.
|
||||
struct RedwoodHeaderV1 {
|
||||
PageType pageType;
|
||||
// The meaning of pageSubType is based on pageType
|
||||
// For Queue pages, pageSubType is the QueueID
|
||||
// For BTree nodes, pageSubType is Height (also stored in BTreeNode)
|
||||
uint8_t pageSubType;
|
||||
// Format identifier, normally specific to the page Type and SubType
|
||||
uint8_t pageFormat;
|
||||
XXH64_hash_t checksum;
|
||||
|
||||
// Physical page ID of first block on disk of the ArenaPage
|
||||
PhysicalPageID firstPhysicalPageID;
|
||||
// The first logical page ID the ArenaPage was referenced by when last written
|
||||
LogicalPageID lastKnownLogicalPageID;
|
||||
// The first logical page ID of the parent of this ArenaPage when last written
|
||||
LogicalPageID lastKnownParentLogicalPageID;
|
||||
|
||||
// Time and write version as of the last update to this page.
|
||||
// Note that for relocated pages, writeVersion should not be updated.
|
||||
double writeTime;
|
||||
Version writeVersion;
|
||||
|
||||
// Update checksum
|
||||
void updateChecksum(uint8_t* headerBytes, int len) {
|
||||
// Checksum is within the checksum input so clear it first
|
||||
checksum = 0;
|
||||
checksum = XXH3_64bits(headerBytes, len);
|
||||
}
|
||||
|
||||
// Verify checksum
|
||||
void verifyChecksum(uint8_t* headerBytes, int len) {
|
||||
// Checksum is within the checksum input so save it and restore it afterwards
|
||||
XXH64_hash_t saved = checksum;
|
||||
checksum = 0;
|
||||
XXH64_hash_t calculated = XXH3_64bits(headerBytes, len);
|
||||
checksum = saved;
|
||||
|
||||
if (saved != calculated) {
|
||||
throw page_header_checksum_failed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// An encoding that validates the payload with an XXHash checksum
|
||||
struct XXHashEncodingHeader {
|
||||
XXH64_hash_t checksum;
|
||||
void encode(uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
}
|
||||
void decode(uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
if (checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
throw page_decoding_failed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// A dummy "encrypting" encoding which uses XOR with a 1 byte secret key on
|
||||
// the payload to obfuscate it and protects the payload with an XXHash checksum.
|
||||
struct XOREncryptionEncodingHeader {
|
||||
// Checksum is on unencrypted payload
|
||||
XXH64_hash_t checksum;
|
||||
uint8_t keyID;
|
||||
|
||||
void encode(uint8_t secret, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
payload[i] ^= secret;
|
||||
}
|
||||
}
|
||||
void decode(uint8_t secret, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
payload[i] ^= secret;
|
||||
}
|
||||
if (checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
throw page_decoding_failed();
|
||||
}
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Get the size of the encoding header based on type
|
||||
// Note that this is only to be used in operations involving new pages to calculate the payload offset. For
|
||||
// existing pages, the payload offset is stored in the page.
|
||||
static int encodingHeaderSize(EncodingType t) {
|
||||
if (t == EncodingType::XXHash64) {
|
||||
return sizeof(XXHashEncodingHeader);
|
||||
} else if (t == EncodingType::XOREncryption) {
|
||||
return sizeof(XOREncryptionEncodingHeader);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t const* begin() const { return (uint8_t*)buffer; }
|
||||
// Get the usable size for a new page of pageSize using HEADER_WRITE_VERSION with encoding type t
|
||||
static int getUsableSize(int pageSize, EncodingType t) {
|
||||
return pageSize - sizeof(PageHeader) - sizeof(RedwoodHeaderV1) - encodingHeaderSize(t);
|
||||
}
|
||||
|
||||
uint8_t* mutate() { return (uint8_t*)buffer; }
|
||||
// Initialize the header for a new page so that the payload can be written to
|
||||
// Pre: Buffer is allocated and logical size is set
|
||||
// Post: Page header is initialized and space is reserved for subheaders for
|
||||
// HEADER_WRITE_VERSION main header and the given encoding type.
|
||||
// Payload can be written to with mutateData() and dataSize()
|
||||
void init(EncodingType t, PageType pageType, uint8_t pageSubType, uint8_t pageFormat = 0) {
|
||||
// Carefully cast away constness to modify page header
|
||||
PageHeader* p = const_cast<PageHeader*>(page);
|
||||
p->headerVersion = HEADER_WRITE_VERSION;
|
||||
p->encodingHeaderOffset = sizeof(PageHeader) + sizeof(RedwoodHeaderV1);
|
||||
p->encodingType = t;
|
||||
p->payloadOffset = page->encodingHeaderOffset + encodingHeaderSize(t);
|
||||
|
||||
typedef XXH64_hash_t Checksum;
|
||||
pPayload = page->getPayload();
|
||||
payloadSize = logicalSize - (pPayload - buffer);
|
||||
|
||||
// Usable size, without checksum
|
||||
int size() const { return logicalSize - sizeof(Checksum); }
|
||||
RedwoodHeaderV1* h = page->getMainHeader<RedwoodHeaderV1>();
|
||||
h->pageType = pageType;
|
||||
h->pageSubType = pageSubType;
|
||||
h->pageFormat = pageFormat;
|
||||
|
||||
Standalone<StringRef> asStringRef() const { return Standalone<StringRef>(StringRef(begin(), size()), arena); }
|
||||
// Write dummy values for these in new pages. They should be updated when possible before calling preWrite()
|
||||
// when modifying existing pages
|
||||
h->lastKnownLogicalPageID = invalidLogicalPageID;
|
||||
h->lastKnownParentLogicalPageID = invalidLogicalPageID;
|
||||
h->writeVersion = invalidVersion;
|
||||
}
|
||||
|
||||
// Get an ArenaPage which is a copy of this page, in its own Arena
|
||||
Reference<ArenaPage> cloneContents() const {
|
||||
// Get the logical page buffer as a StringRef
|
||||
Standalone<StringRef> asStringRef() const { return Standalone<StringRef>(StringRef(buffer, logicalSize)); }
|
||||
|
||||
// Get a new ArenaPage that contains a copy of this page's data.
|
||||
// extra is not copied to the returned page
|
||||
Reference<ArenaPage> clone() const {
|
||||
ArenaPage* p = new ArenaPage(logicalSize, bufferSize);
|
||||
memcpy(p->buffer, buffer, logicalSize);
|
||||
|
||||
// Non-verifying header parse just to initialize members
|
||||
p->postReadHeader(invalidPhysicalPageID, false);
|
||||
p->encryptionKey = encryptionKey;
|
||||
|
||||
return Reference<ArenaPage>(p);
|
||||
}
|
||||
|
||||
// Get an ArenaPage which depends on this page's Arena and references some of its memory
|
||||
Reference<ArenaPage> subPage(int offset, int len) const {
|
||||
Reference<ArenaPage> getSubPage(int offset, int len) const {
|
||||
ASSERT(offset + len <= logicalSize);
|
||||
ArenaPage* p = new ArenaPage(len, 0);
|
||||
p->buffer = buffer + offset;
|
||||
p->arena.dependsOn(arena);
|
||||
|
||||
// Non-verifying header parse just to initialize component pointers
|
||||
p->postReadHeader(invalidPhysicalPageID, false);
|
||||
p->encryptionKey = encryptionKey;
|
||||
|
||||
return Reference<ArenaPage>(p);
|
||||
}
|
||||
|
||||
// Given a vector of pages with the same ->size(), create a new ArenaPage with a ->size() that is
|
||||
// equivalent to all of the input pages and has all of their contents copied into it.
|
||||
static Reference<ArenaPage> concatPages(const std::vector<Reference<const ArenaPage>>& pages) {
|
||||
int usableSize = pages.front()->size();
|
||||
int totalUsableSize = pages.size() * usableSize;
|
||||
int totalBufferSize = pages.front()->bufferSize * pages.size();
|
||||
ArenaPage* superpage = new ArenaPage(totalUsableSize + sizeof(Checksum), totalBufferSize);
|
||||
|
||||
uint8_t* wptr = superpage->mutate();
|
||||
for (auto& p : pages) {
|
||||
ASSERT(p->size() == usableSize);
|
||||
memcpy(wptr, p->begin(), usableSize);
|
||||
wptr += usableSize;
|
||||
// The next two functions set mostly forensic info that may help in an investigation to identify data on disk. The
|
||||
// exception is pageID which must be set to the physical page ID on disk where the page is written or post-read
|
||||
// verification will fail.
|
||||
void setWriteInfo(PhysicalPageID pageID, Version writeVersion) {
|
||||
if (page->headerVersion == 1) {
|
||||
RedwoodHeaderV1* h = page->getMainHeader<RedwoodHeaderV1>();
|
||||
h->firstPhysicalPageID = pageID;
|
||||
h->writeVersion = writeVersion;
|
||||
h->writeTime = now();
|
||||
}
|
||||
|
||||
return Reference<ArenaPage>(superpage);
|
||||
}
|
||||
|
||||
Checksum& getChecksum() { return *(Checksum*)(buffer + size()); }
|
||||
// These should be updated before writing a BTree page. Note that the logical ID that refers to a page can change
|
||||
// after the page is written, if its parent is updated to point directly to its physical page ID. Therefore, the
|
||||
// last known logical page ID should always be updated before writing an updated version of a BTree page.
|
||||
void setLogicalPageInfo(LogicalPageID lastKnownLogicalPageID, LogicalPageID lastKnownParentLogicalPageID) {
|
||||
if (page->headerVersion == 1) {
|
||||
RedwoodHeaderV1* h = page->getMainHeader<RedwoodHeaderV1>();
|
||||
h->lastKnownLogicalPageID = lastKnownLogicalPageID;
|
||||
h->lastKnownParentLogicalPageID = lastKnownParentLogicalPageID;
|
||||
}
|
||||
}
|
||||
|
||||
Checksum calculateChecksum(LogicalPageID pageID) { return XXH3_64bits_withSeed(buffer, size(), pageID); }
|
||||
// Must be called before writing to disk to update headers and encrypt page
|
||||
// Pre: Encoding-specific header fields are set if needed
|
||||
// Secret is set if needed
|
||||
// Post: Main and Encoding subheaders are updated
|
||||
// Payload is possibly encrypted
|
||||
void preWrite(PhysicalPageID pageID) const {
|
||||
// Explicitly check payload definedness to make the source of valgrind errors more clear.
|
||||
// Without this check, calculating a checksum on a payload with undefined bytes does not
|
||||
// cause a valgrind error but the resulting checksum is undefined which causes errors later.
|
||||
ASSERT(VALGRIND_CHECK_MEM_IS_DEFINED(pPayload, payloadSize) == 0);
|
||||
|
||||
void updateChecksum(LogicalPageID pageID) { getChecksum() = calculateChecksum(pageID); }
|
||||
if (page->encodingType == EncodingType::XXHash64) {
|
||||
page->getEncodingHeader<XXHashEncodingHeader>()->encode(pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::XOREncryption) {
|
||||
ASSERT(encryptionKey.secret.size() == 1);
|
||||
XOREncryptionEncodingHeader* xh = page->getEncodingHeader<XOREncryptionEncodingHeader>();
|
||||
xh->keyID = encryptionKey.id.orDefault(0);
|
||||
xh->encode(encryptionKey.secret[0], pPayload, payloadSize, pageID);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
|
||||
bool verifyChecksum(LogicalPageID pageID) { return getChecksum() == calculateChecksum(pageID); }
|
||||
if (page->headerVersion == 1) {
|
||||
page->getMainHeader<RedwoodHeaderV1>()->updateChecksum(buffer, pPayload - buffer);
|
||||
} else {
|
||||
throw page_header_version_not_supported();
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called after reading from disk to verify all non-payload bytes
|
||||
// Pre: Bytes from storage medium copied into raw buffer space
|
||||
// Post: Page headers outside of payload are verified (unless verify is false)
|
||||
// encryptionKey is updated with information from encoding header if needed
|
||||
// Payload is accessible via data(), dataSize(), etc.
|
||||
//
|
||||
// Exceptions are thrown for unknown header types or pages which fail verification
|
||||
void postReadHeader(PhysicalPageID pageID, bool verify = true) {
|
||||
pPayload = page->getPayload();
|
||||
payloadSize = logicalSize - (pPayload - buffer);
|
||||
|
||||
// Populate encryption key with relevant fields from page
|
||||
if (page->encodingType == EncodingType::XOREncryption) {
|
||||
encryptionKey.id = page->getEncodingHeader<XOREncryptionEncodingHeader>()->keyID;
|
||||
}
|
||||
|
||||
if (page->headerVersion == 1) {
|
||||
if (verify) {
|
||||
RedwoodHeaderV1* h = page->getMainHeader<RedwoodHeaderV1>();
|
||||
h->verifyChecksum(buffer, pPayload - buffer);
|
||||
if (pageID != h->firstPhysicalPageID) {
|
||||
throw page_header_wrong_page_id();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw page_header_version_not_supported();
|
||||
}
|
||||
}
|
||||
|
||||
// Pre: postReadHeader has been called, encoding-specific parameters (such as the encryption secret) have been set
|
||||
// Post: Payload has been verified and decrypted if necessary
|
||||
void postReadPayload(PhysicalPageID pageID) {
|
||||
if (page->encodingType == EncodingType::XXHash64) {
|
||||
page->getEncodingHeader<XXHashEncodingHeader>()->decode(pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::XOREncryption) {
|
||||
ASSERT(encryptionKey.secret.size() == 1);
|
||||
page->getEncodingHeader<XOREncryptionEncodingHeader>()->decode(
|
||||
encryptionKey.secret[0], pPayload, payloadSize, pageID);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
}
|
||||
|
||||
const Arena& getArena() const { return arena; }
|
||||
|
||||
static bool isEncodingTypeEncrypted(EncodingType t) { return t == EncodingType::XOREncryption; }
|
||||
|
||||
// Returns true if the page's encoding type employs encryption
|
||||
bool isEncrypted() const { return isEncodingTypeEncrypted(getEncodingType()); }
|
||||
|
||||
private:
|
||||
Arena arena;
|
||||
|
||||
// The logical size of the page, which can be smaller than bufferSize, which is only of
|
||||
// practical purpose in simulation to use arbitrarily small page sizes to test edge cases
|
||||
// with shorter execution time
|
||||
int logicalSize;
|
||||
|
||||
// The 4k-aligned physical size of allocated memory for the page which also represents the
|
||||
// block size to be written to disk
|
||||
int bufferSize;
|
||||
uint8_t* buffer;
|
||||
|
||||
// buffer is a pointer to the page's memory
|
||||
// For convenience, it is unioned with a Page pointer which defines the page structure
|
||||
union {
|
||||
uint8_t* buffer;
|
||||
const PageHeader* page;
|
||||
};
|
||||
|
||||
// Pointer and length of page space available to the user
|
||||
// These are accessed very often so they are stored directly
|
||||
uint8_t* pPayload;
|
||||
int payloadSize;
|
||||
|
||||
public:
|
||||
mutable void* userData;
|
||||
mutable void (*userDataDestructor)(void*);
|
||||
EncodingType getEncodingType() const { return page->encodingType; }
|
||||
|
||||
PhysicalPageID getPhysicalPageID() const {
|
||||
if (page->headerVersion == 1) {
|
||||
return page->getMainHeader<RedwoodHeaderV1>()->firstPhysicalPageID;
|
||||
} else {
|
||||
throw page_header_version_not_supported();
|
||||
}
|
||||
}
|
||||
|
||||
// Used by encodings that do encryption
|
||||
EncryptionKey encryptionKey;
|
||||
|
||||
mutable ArbitraryObject extra;
|
||||
};
|
||||
|
||||
class IPagerSnapshot {
|
||||
|
@ -184,18 +605,21 @@ public:
|
|||
|
||||
virtual void addref() = 0;
|
||||
virtual void delref() = 0;
|
||||
|
||||
ArbitraryObject extra;
|
||||
};
|
||||
|
||||
// This API is probably too customized to the behavior of DWALPager and probably needs some changes to be more generic.
|
||||
class IPager2 : public IClosable {
|
||||
public:
|
||||
virtual std::string getName() const = 0;
|
||||
|
||||
// Returns an ArenaPage that can be passed to writePage. The data in the returned ArenaPage might not be zeroed.
|
||||
virtual Reference<ArenaPage> newPageBuffer(size_t size = 1) = 0;
|
||||
virtual Reference<ArenaPage> newPageBuffer(size_t blocks = 1) = 0;
|
||||
|
||||
// Returns the usable size of pages returned by the pager (i.e. the size of the page that isn't pager overhead).
|
||||
// For a given pager instance, separate calls to this function must return the same value.
|
||||
// Only valid to call after recovery is complete.
|
||||
virtual int getUsablePageSize() const = 0;
|
||||
virtual int getPhysicalPageSize() const = 0;
|
||||
virtual int getLogicalPageSize() const = 0;
|
||||
virtual int getPagesPerExtent() const = 0;
|
||||
|
@ -251,7 +675,7 @@ public:
|
|||
bool noHit) = 0;
|
||||
virtual Future<Reference<ArenaPage>> readMultiPage(PagerEventReasons reason,
|
||||
unsigned int level,
|
||||
Standalone<VectorRef<PhysicalPageID>> pageIDs,
|
||||
VectorRef<PhysicalPageID> pageIDs,
|
||||
int priority,
|
||||
bool cacheable,
|
||||
bool noHit) = 0;
|
||||
|
@ -271,16 +695,13 @@ public:
|
|||
// The snapshot shall be usable until setOldVersion() is called with a version > v.
|
||||
virtual Reference<IPagerSnapshot> getReadSnapshot(Version v) = 0;
|
||||
|
||||
// Atomically make durable all pending page writes, page frees, and update the metadata string,
|
||||
// setting the committed version to v
|
||||
// v must be >= the highest versioned page write.
|
||||
virtual Future<Void> commit(Version v) = 0;
|
||||
// Atomically make durable all pending page writes, page frees, and update the user commit
|
||||
// record at version v
|
||||
// v must be higher than the highest committed version
|
||||
virtual Future<Void> commit(Version v, Value commitRecord) = 0;
|
||||
|
||||
// Get the latest meta key set or committed
|
||||
virtual Key getMetaKey() const = 0;
|
||||
|
||||
// Set the metakey which will be stored in the next commit
|
||||
virtual void setMetaKey(KeyRef metaKey) = 0;
|
||||
// Get the latest committed user commit record
|
||||
virtual Value getCommitRecord() const = 0;
|
||||
|
||||
virtual StorageBytes getStorageBytes() const = 0;
|
||||
|
||||
|
@ -318,4 +739,52 @@ protected:
|
|||
~IPager2() {} // Destruction should be done using close()/dispose() from the IClosable interface
|
||||
};
|
||||
|
||||
// The null key provider is useful to simplify page decoding.
|
||||
// It throws an error for any key info requested.
|
||||
class NullKeyProvider : public IEncryptionKeyProvider {
|
||||
public:
|
||||
virtual ~NullKeyProvider() {}
|
||||
Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override { throw encryption_key_not_found(); }
|
||||
Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) override {
|
||||
throw encryption_key_not_found();
|
||||
}
|
||||
};
|
||||
|
||||
// Key provider for dummy XOR encryption scheme
|
||||
class XOREncryptionKeyProvider : public IEncryptionKeyProvider {
|
||||
public:
|
||||
XOREncryptionKeyProvider(std::string filename) {
|
||||
ASSERT(g_network->isSimulated());
|
||||
|
||||
// Choose a deterministic random filename (without path) byte for secret generation
|
||||
// Remove any leading directory names
|
||||
size_t lastSlash = filename.find_last_of("\\/");
|
||||
if (lastSlash != filename.npos) {
|
||||
filename.erase(0, lastSlash);
|
||||
}
|
||||
xorWith = filename.empty() ? 0x5e
|
||||
: (uint8_t)filename[XXH3_64bits(filename.data(), filename.size()) % filename.size()];
|
||||
}
|
||||
|
||||
virtual ~XOREncryptionKeyProvider() {}
|
||||
|
||||
virtual Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override {
|
||||
if (!key.id.present()) {
|
||||
throw encryption_key_not_found();
|
||||
}
|
||||
EncryptionKey s = key;
|
||||
uint8_t secret = ~(uint8_t)key.id.get() ^ xorWith;
|
||||
s.secret = StringRef(s.arena(), &secret, 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) override {
|
||||
EncryptionKeyRef k;
|
||||
k.id = end.empty() ? 0 : *(end.end() - 1);
|
||||
return getSecrets(k);
|
||||
}
|
||||
|
||||
uint8_t xorWith;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -227,7 +227,8 @@ struct ConfigureDatabaseWorkload : TestWorkload {
|
|||
double testDuration;
|
||||
int additionalDBs;
|
||||
bool allowDescriptorChange;
|
||||
bool allowTestStorageMigration;
|
||||
bool allowTestStorageMigration; // allow change storage migration and perpetual wiggle conf
|
||||
bool storageMigrationCompatibleConf; // only allow generating configuration suitable for storage migration test
|
||||
bool waitStoreTypeCheck;
|
||||
bool downgradeTest1; // if this is true, don't pick up downgrade incompatible config
|
||||
std::vector<Future<Void>> clients;
|
||||
|
@ -239,6 +240,7 @@ struct ConfigureDatabaseWorkload : TestWorkload {
|
|||
getOption(options, LiteralStringRef("allowDescriptorChange"), SERVER_KNOBS->ENABLE_CROSS_CLUSTER_SUPPORT);
|
||||
allowTestStorageMigration =
|
||||
getOption(options, "allowTestStorageMigration"_sr, false) && g_simulator.allowStorageMigrationTypeChange;
|
||||
storageMigrationCompatibleConf = getOption(options, "storageMigrationCompatibleConf"_sr, false);
|
||||
waitStoreTypeCheck = getOption(options, "waitStoreTypeCheck"_sr, false);
|
||||
downgradeTest1 = getOption(options, "downgradeTest1"_sr, false);
|
||||
g_simulator.usableRegions = 1;
|
||||
|
@ -349,7 +351,11 @@ struct ConfigureDatabaseWorkload : TestWorkload {
|
|||
}
|
||||
state int randomChoice;
|
||||
if (self->allowTestStorageMigration) {
|
||||
randomChoice = deterministicRandom()->randomInt(4, 9);
|
||||
randomChoice = (deterministicRandom()->random01() < 0.375) ? deterministicRandom()->randomInt(0, 3)
|
||||
: deterministicRandom()->randomInt(4, 9);
|
||||
} else if (self->storageMigrationCompatibleConf) {
|
||||
randomChoice = (deterministicRandom()->random01() < 3.0 / 7) ? deterministicRandom()->randomInt(0, 3)
|
||||
: deterministicRandom()->randomInt(5, 9);
|
||||
} else {
|
||||
randomChoice = deterministicRandom()->randomInt(0, 8);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "fdbclient/DatabaseContext.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/BlobCipher.h"
|
||||
#include "fdbserver/workloads/workloads.actor.h"
|
||||
|
@ -116,9 +117,10 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
Arena arena;
|
||||
std::unique_ptr<WorkloadMetrics> metrics;
|
||||
|
||||
BlobCipherDomainId minDomainId;
|
||||
BlobCipherDomainId maxDomainId;
|
||||
BlobCipherBaseKeyId minBaseCipherId;
|
||||
EncryptCipherDomainId minDomainId;
|
||||
EncryptCipherDomainId maxDomainId;
|
||||
EncryptCipherBaseKeyId minBaseCipherId;
|
||||
EncryptCipherBaseKeyId headerBaseCipherId;
|
||||
|
||||
EncryptionOpsWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
|
||||
mode = getOption(options, LiteralStringRef("fixedSize"), 1);
|
||||
|
@ -131,6 +133,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
minDomainId = wcx.clientId * 100 + mode * 30 + 1;
|
||||
maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 10) + 5;
|
||||
minBaseCipherId = 100;
|
||||
headerBaseCipherId = wcx.clientId * 100 + 1;
|
||||
|
||||
metrics = std::make_unique<WorkloadMetrics>();
|
||||
|
||||
|
@ -167,17 +170,21 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
|
||||
uint8_t buff[AES_256_KEY_LENGTH];
|
||||
std::vector<Reference<BlobCipherKey>> cipherKeys;
|
||||
for (BlobCipherDomainId id = minDomainId; id <= maxDomainId; id++) {
|
||||
int cipherLen = 0;
|
||||
int cipherLen = 0;
|
||||
for (EncryptCipherDomainId id = minDomainId; id <= maxDomainId; id++) {
|
||||
generateRandomBaseCipher(AES_256_KEY_LENGTH, &buff[0], &cipherLen);
|
||||
cipherKeyCache.insertCipherKey(id, minBaseCipherId, buff, cipherLen);
|
||||
|
||||
ASSERT(cipherLen > 0 && cipherLen <= AES_256_KEY_LENGTH);
|
||||
|
||||
cipherKeys = cipherKeyCache.getAllCiphers(id);
|
||||
ASSERT(cipherKeys.size() == 1);
|
||||
ASSERT_EQ(cipherKeys.size(), 1);
|
||||
}
|
||||
|
||||
// insert the Encrypt Header cipherKey
|
||||
generateRandomBaseCipher(AES_256_KEY_LENGTH, &buff[0], &cipherLen);
|
||||
cipherKeyCache.insertCipherKey(ENCRYPT_HEADER_DOMAIN_ID, headerBaseCipherId, buff, cipherLen);
|
||||
|
||||
TraceEvent("SetupCipherEssentials_Done").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
}
|
||||
|
||||
|
@ -188,10 +195,10 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
TraceEvent("ResetCipherEssentials_Done").log();
|
||||
}
|
||||
|
||||
void updateLatestBaseCipher(const BlobCipherDomainId encryptDomainId,
|
||||
void updateLatestBaseCipher(const EncryptCipherDomainId encryptDomainId,
|
||||
uint8_t* baseCipher,
|
||||
int* baseCipherLen,
|
||||
BlobCipherBaseKeyId* nextBaseCipherId) {
|
||||
EncryptCipherBaseKeyId* nextBaseCipherId) {
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(encryptDomainId);
|
||||
*nextBaseCipherId = cipherKey->getBaseCipherId() + 1;
|
||||
|
@ -202,22 +209,24 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
TraceEvent("UpdateBaseCipher").detail("DomainId", encryptDomainId).detail("BaseCipherId", *nextBaseCipherId);
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> doEncryption(Reference<BlobCipherKey> key,
|
||||
Reference<EncryptBuf> doEncryption(Reference<BlobCipherKey> textCipherKey,
|
||||
Reference<BlobCipherKey> headerCipherKey,
|
||||
uint8_t* payload,
|
||||
int len,
|
||||
const EncryptAuthTokenMode authMode,
|
||||
BlobCipherEncryptHeader* header) {
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
generateRandomData(&iv[0], AES_256_IV_LENGTH);
|
||||
EncryptBlobCipherAes265Ctr encryptor(key, &iv[0], AES_256_IV_LENGTH);
|
||||
EncryptBlobCipherAes265Ctr encryptor(textCipherKey, headerCipherKey, &iv[0], AES_256_IV_LENGTH, authMode);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(payload, len, header, arena);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// validate encrypted buffer size and contents (not matching with plaintext)
|
||||
ASSERT(encrypted->getLogicalSize() == len);
|
||||
ASSERT(memcmp(encrypted->begin(), payload, len) != 0);
|
||||
ASSERT(header->flags.headerVersion == EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), len);
|
||||
ASSERT_NE(memcmp(encrypted->begin(), payload, len), 0);
|
||||
ASSERT_EQ(header->flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
|
||||
metrics->updateEncryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
return encrypted;
|
||||
|
@ -228,23 +237,30 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* originalPayload,
|
||||
Reference<BlobCipherKey> orgCipherKey) {
|
||||
ASSERT(header.flags.headerVersion == EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT(header.flags.encryptMode == BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getCipherKey(header.encryptDomainId, header.baseCipherId);
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache.getCipherKey(
|
||||
header.cipherHeaderDetails.encryptDomainId, header.cipherHeaderDetails.baseCipherId);
|
||||
ASSERT(cipherKey.isValid());
|
||||
ASSERT(cipherKey->isEqual(orgCipherKey));
|
||||
|
||||
DecryptBlobCipherAes256Ctr decryptor(cipherKey, &header.iv[0]);
|
||||
DecryptBlobCipherAes256Ctr decryptor(cipherKey, headerCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
const bool validateHeaderAuthToken = deterministicRandom()->randomInt(0, 100) < 65;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
if (validateHeaderAuthToken) {
|
||||
decryptor.verifyHeaderAuthToken(header, arena);
|
||||
}
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), len, header, arena);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// validate decrypted buffer size and contents (matching with original plaintext)
|
||||
ASSERT(decrypted->getLogicalSize() == len);
|
||||
ASSERT(memcmp(decrypted->begin(), originalPayload, len) == 0);
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), len);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), originalPayload, len), 0);
|
||||
|
||||
metrics->updateDecryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
}
|
||||
|
@ -256,7 +272,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
Future<Void> start(Database const& cx) override {
|
||||
uint8_t baseCipher[AES_256_KEY_LENGTH];
|
||||
int baseCipherLen = 0;
|
||||
BlobCipherBaseKeyId nextBaseCipherId;
|
||||
EncryptCipherBaseKeyId nextBaseCipherId;
|
||||
|
||||
// Setup encryptDomainIds and corresponding baseCipher details
|
||||
setupCipherEssentials();
|
||||
|
@ -268,7 +284,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
// randomly select a domainId
|
||||
const BlobCipherDomainId encryptDomainId = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
const EncryptCipherDomainId encryptDomainId = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
ASSERT(encryptDomainId >= minDomainId && encryptDomainId <= maxDomainId);
|
||||
|
||||
if (updateBaseCipher) {
|
||||
|
@ -279,14 +295,17 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(encryptDomainId);
|
||||
// Each client working with their own version of encryptHeaderCipherKey, avoid using getLatest()
|
||||
Reference<BlobCipherKey> headerCipherKey =
|
||||
cipherKeyCache.getCipherKey(ENCRYPT_HEADER_DOMAIN_ID, headerBaseCipherId);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
metrics->updateKeyDerivationTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
|
||||
// Validate sanity of "getLatestCipher", especially when baseCipher gets updated
|
||||
if (updateBaseCipher) {
|
||||
ASSERT(cipherKey->getBaseCipherId() == nextBaseCipherId);
|
||||
ASSERT(cipherKey->getBaseCipherLen() == baseCipherLen);
|
||||
ASSERT(memcmp(cipherKey->rawBaseCipher(), baseCipher, baseCipherLen) == 0);
|
||||
ASSERT_EQ(cipherKey->getBaseCipherId(), nextBaseCipherId);
|
||||
ASSERT_EQ(cipherKey->getBaseCipherLen(), baseCipherLen);
|
||||
ASSERT_EQ(memcmp(cipherKey->rawBaseCipher(), baseCipher, baseCipherLen), 0);
|
||||
}
|
||||
|
||||
int dataLen = isFixedSizePayload() ? pageSize : deterministicRandom()->randomInt(100, maxBufSize);
|
||||
|
@ -294,8 +313,12 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
|
||||
// Encrypt the payload - generates BlobCipherEncryptHeader to assist decryption later
|
||||
BlobCipherEncryptHeader header;
|
||||
const EncryptAuthTokenMode authMode = deterministicRandom()->randomInt(0, 100) < 50
|
||||
? ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
: ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI;
|
||||
try {
|
||||
Reference<EncryptBuf> encrypted = doEncryption(cipherKey, buff.get(), dataLen, &header);
|
||||
Reference<EncryptBuf> encrypted =
|
||||
doEncryption(cipherKey, headerCipherKey, buff.get(), dataLen, authMode, &header);
|
||||
|
||||
// Decrypt the payload - parses the BlobCipherEncryptHeader, fetch corresponding cipherKey and
|
||||
// decrypt
|
||||
|
@ -303,7 +326,8 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
} catch (Error& e) {
|
||||
TraceEvent("Failed")
|
||||
.detail("DomainId", encryptDomainId)
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId());
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId())
|
||||
.detail("AuthMode", authMode);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "flow/BlobCipher.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/IRandom.h"
|
||||
|
@ -29,21 +30,23 @@
|
|||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#if ENCRYPTION_ENABLED
|
||||
|
||||
// BlobCipherEncryptHeader
|
||||
BlobCipherEncryptHeader::BlobCipherEncryptHeader() {
|
||||
flags.encryptMode = BLOB_CIPHER_ENCRYPT_MODE_NONE;
|
||||
namespace {
|
||||
bool isEncryptHeaderAuthTokenModeValid(const EncryptAuthTokenMode mode) {
|
||||
return mode >= ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && mode < ENCRYPT_HEADER_AUTH_TOKEN_LAST;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// BlobCipherKey class methods
|
||||
|
||||
BlobCipherKey::BlobCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
BlobCipherKey::BlobCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCiphId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen) {
|
||||
BlobCipherRandomSalt salt;
|
||||
EncryptCipherRandomSalt salt;
|
||||
if (g_network->isSimulated()) {
|
||||
salt = deterministicRandom()->randomUInt64();
|
||||
} else {
|
||||
|
@ -58,11 +61,11 @@ BlobCipherKey::BlobCipherKey(const BlobCipherDomainId& domainId,
|
|||
.detail("CreationTime", creationTime);*/
|
||||
}
|
||||
|
||||
void BlobCipherKey::initKey(const BlobCipherDomainId& domainId,
|
||||
void BlobCipherKey::initKey(const EncryptCipherDomainId& domainId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
const BlobCipherRandomSalt& salt) {
|
||||
const EncryptCipherBaseKeyId& baseCiphId,
|
||||
const EncryptCipherRandomSalt& salt) {
|
||||
// Set the base encryption key properties
|
||||
baseCipher = std::make_unique<uint8_t[]>(AES_256_KEY_LENGTH);
|
||||
memset(baseCipher.get(), 0, AES_256_KEY_LENGTH);
|
||||
|
@ -82,11 +85,11 @@ void BlobCipherKey::initKey(const BlobCipherDomainId& domainId,
|
|||
|
||||
void BlobCipherKey::applyHmacSha256Derivation() {
|
||||
Arena arena;
|
||||
uint8_t buf[baseCipherLen + sizeof(BlobCipherRandomSalt)];
|
||||
uint8_t buf[baseCipherLen + sizeof(EncryptCipherRandomSalt)];
|
||||
memcpy(&buf[0], baseCipher.get(), baseCipherLen);
|
||||
memcpy(&buf[0] + baseCipherLen, &randomSalt, sizeof(BlobCipherRandomSalt));
|
||||
memcpy(&buf[0] + baseCipherLen, &randomSalt, sizeof(EncryptCipherRandomSalt));
|
||||
HmacSha256DigestGen hmacGen(baseCipher.get(), baseCipherLen);
|
||||
StringRef digest = hmacGen.digest(&buf[0], baseCipherLen + sizeof(BlobCipherRandomSalt), arena);
|
||||
StringRef digest = hmacGen.digest(&buf[0], baseCipherLen + sizeof(EncryptCipherRandomSalt), arena);
|
||||
std::copy(digest.begin(), digest.end(), cipher.get());
|
||||
if (digest.size() < AES_256_KEY_LENGTH) {
|
||||
memcpy(cipher.get() + digest.size(), buf, AES_256_KEY_LENGTH - digest.size());
|
||||
|
@ -101,10 +104,10 @@ void BlobCipherKey::reset() {
|
|||
// BlobKeyIdCache class methods
|
||||
|
||||
BlobCipherKeyIdCache::BlobCipherKeyIdCache()
|
||||
: domainId(INVALID_DOMAIN_ID), latestBaseCipherKeyId(INVALID_CIPHER_KEY_ID) {}
|
||||
: domainId(ENCRYPT_INVALID_DOMAIN_ID), latestBaseCipherKeyId(ENCRYPT_INVALID_CIPHER_KEY_ID) {}
|
||||
|
||||
BlobCipherKeyIdCache::BlobCipherKeyIdCache(BlobCipherDomainId dId)
|
||||
: domainId(dId), latestBaseCipherKeyId(INVALID_CIPHER_KEY_ID) {
|
||||
BlobCipherKeyIdCache::BlobCipherKeyIdCache(EncryptCipherDomainId dId)
|
||||
: domainId(dId), latestBaseCipherKeyId(ENCRYPT_INVALID_CIPHER_KEY_ID) {
|
||||
TraceEvent("Init_BlobCipherKeyIdCache").detail("DomainId", domainId);
|
||||
}
|
||||
|
||||
|
@ -112,7 +115,7 @@ Reference<BlobCipherKey> BlobCipherKeyIdCache::getLatestCipherKey() {
|
|||
return getCipherByBaseCipherId(latestBaseCipherKeyId);
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyIdCache::getCipherByBaseCipherId(BlobCipherBaseKeyId baseCipherKeyId) {
|
||||
Reference<BlobCipherKey> BlobCipherKeyIdCache::getCipherByBaseCipherId(EncryptCipherBaseKeyId baseCipherKeyId) {
|
||||
BlobCipherKeyIdCacheMapCItr itr = keyIdCache.find(baseCipherKeyId);
|
||||
if (itr == keyIdCache.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
|
@ -120,10 +123,10 @@ Reference<BlobCipherKey> BlobCipherKeyIdCache::getCipherByBaseCipherId(BlobCiphe
|
|||
return itr->second;
|
||||
}
|
||||
|
||||
void BlobCipherKeyIdCache::insertBaseCipherKey(BlobCipherBaseKeyId baseCipherId,
|
||||
void BlobCipherKeyIdCache::insertBaseCipherKey(EncryptCipherBaseKeyId baseCipherId,
|
||||
const uint8_t* baseCipher,
|
||||
int baseCipherLen) {
|
||||
ASSERT(baseCipherId > INVALID_CIPHER_KEY_ID);
|
||||
ASSERT_GT(baseCipherId, ENCRYPT_INVALID_CIPHER_KEY_ID);
|
||||
|
||||
// BaseCipherKeys are immutable, ensure that cached value doesn't get updated.
|
||||
BlobCipherKeyIdCacheMapCItr itr = keyIdCache.find(baseCipherId);
|
||||
|
@ -165,11 +168,11 @@ std::vector<Reference<BlobCipherKey>> BlobCipherKeyIdCache::getAllCipherKeys() {
|
|||
|
||||
// BlobCipherKeyCache class methods
|
||||
|
||||
void BlobCipherKeyCache::insertCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCipherId,
|
||||
void BlobCipherKeyCache::insertCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCipherId,
|
||||
const uint8_t* baseCipher,
|
||||
int baseCipherLen) {
|
||||
if (domainId == INVALID_DOMAIN_ID || baseCipherId == INVALID_CIPHER_KEY_ID) {
|
||||
if (domainId == ENCRYPT_INVALID_DOMAIN_ID || baseCipherId == ENCRYPT_INVALID_CIPHER_KEY_ID) {
|
||||
throw encrypt_invalid_id();
|
||||
}
|
||||
|
||||
|
@ -193,7 +196,7 @@ void BlobCipherKeyCache::insertCipherKey(const BlobCipherDomainId& domainId,
|
|||
}
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getLatestCipherKey(const BlobCipherDomainId& domainId) {
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getLatestCipherKey(const EncryptCipherDomainId& domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
TraceEvent("GetLatestCipherKey_DomainNotFound").detail("DomainId", domainId);
|
||||
|
@ -212,8 +215,8 @@ Reference<BlobCipherKey> BlobCipherKeyCache::getLatestCipherKey(const BlobCipher
|
|||
return cipherKey;
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCipherId) {
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCipherId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
|
@ -223,7 +226,7 @@ Reference<BlobCipherKey> BlobCipherKeyCache::getCipherKey(const BlobCipherDomain
|
|||
return keyIdCache->getCipherByBaseCipherId(baseCipherId);
|
||||
}
|
||||
|
||||
void BlobCipherKeyCache::resetEncyrptDomainId(const BlobCipherDomainId domainId) {
|
||||
void BlobCipherKeyCache::resetEncyrptDomainId(const EncryptCipherDomainId domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
|
@ -245,7 +248,7 @@ void BlobCipherKeyCache::cleanup() noexcept {
|
|||
instance.domainCacheMap.clear();
|
||||
}
|
||||
|
||||
std::vector<Reference<BlobCipherKey>> BlobCipherKeyCache::getAllCiphers(const BlobCipherDomainId& domainId) {
|
||||
std::vector<Reference<BlobCipherKey>> BlobCipherKeyCache::getAllCiphers(const EncryptCipherDomainId& domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
return {};
|
||||
|
@ -255,13 +258,17 @@ std::vector<Reference<BlobCipherKey>> BlobCipherKeyCache::getAllCiphers(const Bl
|
|||
return keyIdCache->getAllCipherKeys();
|
||||
}
|
||||
|
||||
// EncryptBlobCipher class methods
|
||||
// EncryptBlobCipherAes265Ctr class methods
|
||||
|
||||
EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> key,
|
||||
EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* cipherIV,
|
||||
const int ivLen)
|
||||
: ctx(EVP_CIPHER_CTX_new()), cipherKey(key) {
|
||||
ASSERT(ivLen == AES_256_IV_LENGTH);
|
||||
const int ivLen,
|
||||
const EncryptAuthTokenMode mode)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey), authTokenMode(mode) {
|
||||
ASSERT(isEncryptHeaderAuthTokenModeValid(mode));
|
||||
ASSERT_EQ(ivLen, AES_256_IV_LENGTH);
|
||||
|
||||
memcpy(&iv[0], cipherIV, ivLen);
|
||||
|
||||
if (ctx == nullptr) {
|
||||
|
@ -270,7 +277,7 @@ EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey>
|
|||
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, nullptr, nullptr) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.getPtr()->data(), cipherIV) != 1) {
|
||||
if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, textCipherKey.getPtr()->data(), cipherIV) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
@ -281,21 +288,29 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
Arena& arena) {
|
||||
TEST(true); // Encrypting data with BlobCipher
|
||||
|
||||
Reference<EncryptBuf> encryptBuf = makeReference<EncryptBuf>(plaintextLen + AES_BLOCK_SIZE, arena);
|
||||
memset(reinterpret_cast<uint8_t*>(header), 0, sizeof(BlobCipherEncryptHeader));
|
||||
|
||||
// Alloc buffer computation accounts for 'header authentication' generation scheme. If single-auth-token needs to be
|
||||
// generated, allocate buffer sufficient to append header to the cipherText optimizing memcpy cost.
|
||||
|
||||
const int allocSize = authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
? plaintextLen + AES_BLOCK_SIZE + sizeof(BlobCipherEncryptHeader)
|
||||
: plaintextLen + AES_BLOCK_SIZE;
|
||||
Reference<EncryptBuf> encryptBuf = makeReference<EncryptBuf>(allocSize, arena);
|
||||
uint8_t* ciphertext = encryptBuf->begin();
|
||||
int bytes{ 0 };
|
||||
if (EVP_EncryptUpdate(ctx, ciphertext, &bytes, plaintext, plaintextLen) != 1) {
|
||||
TraceEvent("Encrypt_UpdateFailed")
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", cipherKey->getDomainId());
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
int finalBytes{ 0 };
|
||||
if (EVP_EncryptFinal_ex(ctx, ciphertext + bytes, &finalBytes) != 1) {
|
||||
TraceEvent("Encrypt_FinalFailed")
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", cipherKey->getDomainId());
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
|
@ -306,19 +321,57 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// populate header details for the encrypted blob.
|
||||
// Populate encryption header flags details
|
||||
header->flags.size = sizeof(BlobCipherEncryptHeader);
|
||||
header->flags.headerVersion = EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION;
|
||||
header->flags.encryptMode = BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR;
|
||||
header->baseCipherId = cipherKey->getBaseCipherId();
|
||||
header->encryptDomainId = cipherKey->getDomainId();
|
||||
header->salt = cipherKey->getSalt();
|
||||
memcpy(&header->iv[0], &iv[0], AES_256_IV_LENGTH);
|
||||
header->flags.encryptMode = ENCRYPT_CIPHER_MODE_AES_256_CTR;
|
||||
header->flags.authTokenMode = authTokenMode;
|
||||
|
||||
// Preserve checksum of encrypted bytes in the header; approach protects against disk induced bit-rot/flip
|
||||
// scenarios. AES CTR mode doesn't generate 'tag' by default as with schemes such as: AES 256 GCM.
|
||||
// Populate cipherText encryption-key details
|
||||
header->cipherTextDetails.baseCipherId = textCipherKey->getBaseCipherId();
|
||||
header->cipherTextDetails.encryptDomainId = textCipherKey->getDomainId();
|
||||
header->cipherTextDetails.salt = textCipherKey->getSalt();
|
||||
memcpy(&header->cipherTextDetails.iv[0], &iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
header->ciphertextChecksum = computeEncryptChecksum(ciphertext, bytes + finalBytes, cipherKey->getSalt(), arena);
|
||||
if (authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
// No header 'authToken' generation needed.
|
||||
} else {
|
||||
// Populate header encryption-key details
|
||||
header->cipherHeaderDetails.encryptDomainId = headerCipherKey->getDomainId();
|
||||
header->cipherHeaderDetails.baseCipherId = headerCipherKey->getBaseCipherId();
|
||||
|
||||
// Populate header authToken details
|
||||
if (header->flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
ASSERT_GE(allocSize, (bytes + finalBytes + sizeof(BlobCipherEncryptHeader)));
|
||||
ASSERT_GE(encryptBuf->getLogicalSize(), (bytes + finalBytes + sizeof(BlobCipherEncryptHeader)));
|
||||
|
||||
memcpy(&ciphertext[bytes + finalBytes],
|
||||
reinterpret_cast<const uint8_t*>(header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
StringRef authToken = computeAuthToken(ciphertext,
|
||||
bytes + finalBytes + sizeof(BlobCipherEncryptHeader),
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
arena);
|
||||
memcpy(&header->singleAuthToken.authToken[0], authToken.begin(), AUTH_TOKEN_SIZE);
|
||||
} else {
|
||||
ASSERT_EQ(header->flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
|
||||
StringRef cipherTextAuthToken =
|
||||
computeAuthToken(ciphertext,
|
||||
bytes + finalBytes,
|
||||
reinterpret_cast<const uint8_t*>(&header->cipherTextDetails.salt),
|
||||
sizeof(EncryptCipherRandomSalt),
|
||||
arena);
|
||||
memcpy(&header->multiAuthTokens.cipherTextAuthToken[0], cipherTextAuthToken.begin(), AUTH_TOKEN_SIZE);
|
||||
StringRef headerAuthToken = computeAuthToken(reinterpret_cast<const uint8_t*>(header),
|
||||
sizeof(BlobCipherEncryptHeader),
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
arena);
|
||||
memcpy(&header->multiAuthTokens.headerAuthToken[0], headerAuthToken.begin(), AUTH_TOKEN_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
encryptBuf->setLogicalSize(plaintextLen);
|
||||
return encryptBuf;
|
||||
|
@ -330,45 +383,137 @@ EncryptBlobCipherAes265Ctr::~EncryptBlobCipherAes265Ctr() {
|
|||
}
|
||||
}
|
||||
|
||||
// DecryptBlobCipher class methods
|
||||
// DecryptBlobCipherAes256Ctr class methods
|
||||
|
||||
DecryptBlobCipherAes256Ctr::DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> key, const uint8_t* iv)
|
||||
: ctx(EVP_CIPHER_CTX_new()) {
|
||||
DecryptBlobCipherAes256Ctr::DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* iv)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey),
|
||||
headerAuthTokenValidationDone(false), authTokensValidationDone(false) {
|
||||
if (ctx == nullptr) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, nullptr, nullptr)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.getPtr()->data(), iv)) {
|
||||
if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, tCipherKey.getPtr()->data(), iv)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::verifyEncryptBlobHeader(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena& arena) {
|
||||
// validate header flag sanity
|
||||
if (header.flags.headerVersion != EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION ||
|
||||
header.flags.encryptMode != BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR) {
|
||||
TraceEvent("VerifyEncryptBlobHeader")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("ExpectedVersion", EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION)
|
||||
.detail("ExpectedMode", BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR);
|
||||
throw encrypt_header_metadata_mismatch();
|
||||
void DecryptBlobCipherAes256Ctr::verifyHeaderAuthToken(const BlobCipherEncryptHeader& header, Arena& arena) {
|
||||
if (header.flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI) {
|
||||
// NoneAuthToken mode; no authToken is generated; nothing to do
|
||||
// SingleAuthToken mode; verification will happen as part of decryption.
|
||||
return;
|
||||
}
|
||||
|
||||
// encrypted byte checksum sanity; protection against data bit-rot/flip.
|
||||
BlobCipherChecksum computed = computeEncryptChecksum(ciphertext, ciphertextLen, header.salt, arena);
|
||||
if (computed != header.ciphertextChecksum) {
|
||||
TraceEvent("VerifyEncryptBlobHeader_ChecksumMismatch")
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
|
||||
BlobCipherEncryptHeader headerCopy;
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
memset(reinterpret_cast<uint8_t*>(&headerCopy.multiAuthTokens.headerAuthToken), 0, AUTH_TOKEN_SIZE);
|
||||
StringRef computedHeaderAuthToken = computeAuthToken(reinterpret_cast<const uint8_t*>(&headerCopy),
|
||||
sizeof(BlobCipherEncryptHeader),
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
arena);
|
||||
if (memcmp(&header.multiAuthTokens.headerAuthToken[0], computedHeaderAuthToken.begin(), AUTH_TOKEN_SIZE) != 0) {
|
||||
TraceEvent("VerifyEncryptBlobHeader_AuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("CiphertextChecksum", header.ciphertextChecksum)
|
||||
.detail("ComputedCiphertextChecksum", computed);
|
||||
throw encrypt_header_checksum_mismatch();
|
||||
.detail("MultiAuthHeaderAuthToken",
|
||||
StringRef(arena, &header.multiAuthTokens.headerAuthToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedHeaderAuthToken", computedHeaderAuthToken.toString());
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
|
||||
headerAuthTokenValidationDone = true;
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::verifyHeaderSingleAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
// Header authToken not set for single auth-token mode.
|
||||
ASSERT(!headerAuthTokenValidationDone);
|
||||
|
||||
// prepare the payload {cipherText + encryptionHeader}
|
||||
memcpy(&buff[0], ciphertext, ciphertextLen);
|
||||
memcpy(&buff[ciphertextLen], reinterpret_cast<const uint8_t*>(&header), sizeof(BlobCipherEncryptHeader));
|
||||
// ensure the 'authToken' is reset before computing the 'authentication token'
|
||||
BlobCipherEncryptHeader* eHeader = (BlobCipherEncryptHeader*)(&buff[ciphertextLen]);
|
||||
memset(reinterpret_cast<uint8_t*>(&eHeader->singleAuthToken), 0, 2 * AUTH_TOKEN_SIZE);
|
||||
|
||||
StringRef computed = computeAuthToken(
|
||||
buff, ciphertextLen + sizeof(BlobCipherEncryptHeader), headerCipherKey->rawCipher(), AES_256_KEY_LENGTH, arena);
|
||||
if (memcmp(&header.singleAuthToken.authToken[0], computed.begin(), AUTH_TOKEN_SIZE) != 0) {
|
||||
TraceEvent("VerifyEncryptBlobHeader_AuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("SingleAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedSingleAuthToken", computed.toString());
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::verifyHeaderMultiAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
if (!headerAuthTokenValidationDone) {
|
||||
verifyHeaderAuthToken(header, arena);
|
||||
}
|
||||
StringRef computedCipherTextAuthToken =
|
||||
computeAuthToken(ciphertext,
|
||||
ciphertextLen,
|
||||
reinterpret_cast<const uint8_t*>(&header.cipherTextDetails.salt),
|
||||
sizeof(EncryptCipherRandomSalt),
|
||||
arena);
|
||||
if (memcmp(&header.multiAuthTokens.cipherTextAuthToken[0], computedCipherTextAuthToken.begin(), AUTH_TOKEN_SIZE) !=
|
||||
0) {
|
||||
TraceEvent("VerifyEncryptBlobHeader_AuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("MultiAuthCipherTextAuthToken",
|
||||
StringRef(arena, &header.multiAuthTokens.cipherTextAuthToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedCipherTextAuthToken", computedCipherTextAuthToken.toString());
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::verifyAuthTokens(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
if (header.flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
verifyHeaderSingleAuthToken(ciphertext, ciphertextLen, header, buff, arena);
|
||||
} else {
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
verifyHeaderMultiAuthToken(ciphertext, ciphertextLen, header, buff, arena);
|
||||
}
|
||||
|
||||
authTokensValidationDone = true;
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::verifyEncryptHeaderMetadata(const BlobCipherEncryptHeader& header) {
|
||||
// validate header flag sanity
|
||||
if (header.flags.headerVersion != EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION ||
|
||||
header.flags.encryptMode != ENCRYPT_CIPHER_MODE_AES_256_CTR ||
|
||||
!isEncryptHeaderAuthTokenModeValid((EncryptAuthTokenMode)header.flags.authTokenMode)) {
|
||||
TraceEvent("VerifyEncryptBlobHeader")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("ExpectedVersion", EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION)
|
||||
.detail("EncryptCipherMode", header.flags.encryptMode)
|
||||
.detail("ExpectedCipherMode", ENCRYPT_CIPHER_MODE_AES_256_CTR)
|
||||
.detail("EncryptHeaderAuthTokenMode", header.flags.authTokenMode);
|
||||
throw encrypt_header_metadata_mismatch();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,23 +523,37 @@ Reference<EncryptBuf> DecryptBlobCipherAes256Ctr::decrypt(const uint8_t* ciphert
|
|||
Arena& arena) {
|
||||
TEST(true); // Decrypting data with BlobCipher
|
||||
|
||||
verifyEncryptBlobHeader(ciphertext, ciphertextLen, header, arena);
|
||||
verifyEncryptHeaderMetadata(header);
|
||||
|
||||
if (header.flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && !headerCipherKey.isValid()) {
|
||||
TraceEvent("Decrypt_InvalidHeaderCipherKey").detail("AuthTokenMode", header.flags.authTokenMode);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
const int allocSize = header.flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
? ciphertextLen + AES_BLOCK_SIZE + sizeof(BlobCipherEncryptHeader)
|
||||
: ciphertextLen + AES_BLOCK_SIZE;
|
||||
Reference<EncryptBuf> decrypted = makeReference<EncryptBuf>(allocSize, arena);
|
||||
|
||||
if (header.flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
verifyAuthTokens(ciphertext, ciphertextLen, header, decrypted->begin(), arena);
|
||||
ASSERT(authTokensValidationDone);
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> decrypted = makeReference<EncryptBuf>(ciphertextLen + AES_BLOCK_SIZE, arena);
|
||||
uint8_t* plaintext = decrypted->begin();
|
||||
int bytesDecrypted{ 0 };
|
||||
if (!EVP_DecryptUpdate(ctx, plaintext, &bytesDecrypted, ciphertext, ciphertextLen)) {
|
||||
TraceEvent("Decrypt_UpdateFailed")
|
||||
.detail("BaseCipherId", header.baseCipherId)
|
||||
.detail("EncryptDomainId", header.encryptDomainId);
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("EncryptDomainId", header.cipherTextDetails.encryptDomainId);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
int finalBlobBytes{ 0 };
|
||||
if (EVP_DecryptFinal_ex(ctx, plaintext + bytesDecrypted, &finalBlobBytes) <= 0) {
|
||||
TraceEvent("Decrypt_FinalFailed")
|
||||
.detail("BaseCipherId", header.baseCipherId)
|
||||
.detail("EncryptDomainId", header.encryptDomainId);
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("EncryptDomainId", header.cipherTextDetails.encryptDomainId);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
|
@ -443,6 +602,18 @@ StringRef HmacSha256DigestGen::digest(const unsigned char* data, size_t len, Are
|
|||
return StringRef(digest, digestLen);
|
||||
}
|
||||
|
||||
StringRef computeAuthToken(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
const uint8_t* key,
|
||||
const int keyLen,
|
||||
Arena& arena) {
|
||||
HmacSha256DigestGen hmacGenerator(key, keyLen);
|
||||
StringRef digest = hmacGenerator.digest(payload, payloadLen, arena);
|
||||
|
||||
ASSERT_GE(digest.size(), AUTH_TOKEN_SIZE);
|
||||
return digest;
|
||||
}
|
||||
|
||||
// Only used to link unit tests
|
||||
void forceLinkBlobCipherTests() {}
|
||||
|
||||
|
@ -453,41 +624,42 @@ void forceLinkBlobCipherTests() {}
|
|||
// 4. Inserting of 'non-identical' cipherKey (already cached) more than once works as desired.
|
||||
// 5. Validation encryption ops (correctness):
|
||||
// 5.1. Encyrpt a buffer followed by decryption of the buffer, validate the contents.
|
||||
// 5.2. Simulate anomolies such as: EncyrptionHeader corruption, checkSum mismatch / encryptionMode mismatch etc.
|
||||
// 5.2. Simulate anomalies such as: EncyrptionHeader corruption, authToken mismatch / encryptionMode mismatch etc.
|
||||
// 6. Cache cleanup
|
||||
// 6.1 cleanup cipherKeys by given encryptDomainId
|
||||
// 6.2. Cleanup all cached cipherKeys
|
||||
TEST_CASE("flow/BlobCipher") {
|
||||
TraceEvent("BlobCipherTest_Start").log();
|
||||
|
||||
// Construct a dummy External Key Manager representation and populate with some keys
|
||||
class BaseCipher : public ReferenceCounted<BaseCipher>, NonCopyable {
|
||||
public:
|
||||
BlobCipherDomainId domainId;
|
||||
EncryptCipherDomainId domainId;
|
||||
int len;
|
||||
BlobCipherBaseKeyId keyId;
|
||||
EncryptCipherBaseKeyId keyId;
|
||||
std::unique_ptr<uint8_t[]> key;
|
||||
|
||||
BaseCipher(const BlobCipherDomainId& dId, const BlobCipherBaseKeyId& kId)
|
||||
BaseCipher(const EncryptCipherDomainId& dId, const EncryptCipherBaseKeyId& kId)
|
||||
: domainId(dId), len(deterministicRandom()->randomInt(AES_256_KEY_LENGTH / 2, AES_256_KEY_LENGTH + 1)),
|
||||
keyId(kId), key(std::make_unique<uint8_t[]>(len)) {
|
||||
generateRandomData(key.get(), len);
|
||||
}
|
||||
};
|
||||
|
||||
using BaseKeyMap = std::unordered_map<BlobCipherBaseKeyId, Reference<BaseCipher>>;
|
||||
using DomainKeyMap = std::unordered_map<BlobCipherDomainId, BaseKeyMap>;
|
||||
using BaseKeyMap = std::unordered_map<EncryptCipherBaseKeyId, Reference<BaseCipher>>;
|
||||
using DomainKeyMap = std::unordered_map<EncryptCipherDomainId, BaseKeyMap>;
|
||||
DomainKeyMap domainKeyMap;
|
||||
const BlobCipherDomainId minDomainId = 1;
|
||||
const BlobCipherDomainId maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 10) + 5;
|
||||
const BlobCipherBaseKeyId minBaseCipherKeyId = 100;
|
||||
const BlobCipherBaseKeyId maxBaseCipherKeyId =
|
||||
const EncryptCipherDomainId minDomainId = 1;
|
||||
const EncryptCipherDomainId maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 10) + 5;
|
||||
const EncryptCipherBaseKeyId minBaseCipherKeyId = 100;
|
||||
const EncryptCipherBaseKeyId maxBaseCipherKeyId =
|
||||
deterministicRandom()->randomInt(minBaseCipherKeyId, minBaseCipherKeyId + 50) + 15;
|
||||
for (int dId = minDomainId; dId <= maxDomainId; dId++) {
|
||||
for (int kId = minBaseCipherKeyId; kId <= maxBaseCipherKeyId; kId++) {
|
||||
domainKeyMap[dId].emplace(kId, makeReference<BaseCipher>(dId, kId));
|
||||
}
|
||||
}
|
||||
ASSERT(domainKeyMap.size() == maxDomainId);
|
||||
ASSERT_EQ(domainKeyMap.size(), maxDomainId);
|
||||
|
||||
// insert BlobCipher keys into BlobCipherKeyCache map and validate
|
||||
TraceEvent("BlobCipherTest_InsertKeys").log();
|
||||
|
@ -500,6 +672,11 @@ TEST_CASE("flow/BlobCipher") {
|
|||
baseCipher->domainId, baseCipher->keyId, baseCipher->key.get(), baseCipher->len);
|
||||
}
|
||||
}
|
||||
// insert EncryptHeader BlobCipher key
|
||||
Reference<BaseCipher> headerBaseCipher = makeReference<BaseCipher>(ENCRYPT_HEADER_DOMAIN_ID, 1);
|
||||
cipherKeyCache.insertCipherKey(
|
||||
headerBaseCipher->domainId, headerBaseCipher->keyId, headerBaseCipher->key.get(), headerBaseCipher->len);
|
||||
|
||||
TraceEvent("BlobCipherTest_InsertKeysDone").log();
|
||||
|
||||
// validate the cipherKey lookups work as desired
|
||||
|
@ -509,13 +686,13 @@ TEST_CASE("flow/BlobCipher") {
|
|||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getCipherKey(baseCipher->domainId, baseCipher->keyId);
|
||||
ASSERT(cipherKey.isValid());
|
||||
// validate common cipher properties - domainId, baseCipherId, baseCipherLen, rawBaseCipher
|
||||
ASSERT(cipherKey->getBaseCipherId() == baseCipher->keyId);
|
||||
ASSERT(cipherKey->getDomainId() == baseCipher->domainId);
|
||||
ASSERT(cipherKey->getBaseCipherLen() == baseCipher->len);
|
||||
ASSERT_EQ(cipherKey->getBaseCipherId(), baseCipher->keyId);
|
||||
ASSERT_EQ(cipherKey->getDomainId(), baseCipher->domainId);
|
||||
ASSERT_EQ(cipherKey->getBaseCipherLen(), baseCipher->len);
|
||||
// ensure that baseCipher matches with the cached information
|
||||
ASSERT(std::memcmp(cipherKey->rawBaseCipher(), baseCipher->key.get(), cipherKey->getBaseCipherLen()) == 0);
|
||||
ASSERT_EQ(std::memcmp(cipherKey->rawBaseCipher(), baseCipher->key.get(), cipherKey->getBaseCipherLen()), 0);
|
||||
// validate the encryption derivation
|
||||
ASSERT(std::memcmp(cipherKey->rawCipher(), baseCipher->key.get(), cipherKey->getBaseCipherLen()) != 0);
|
||||
ASSERT_NE(std::memcmp(cipherKey->rawCipher(), baseCipher->key.get(), cipherKey->getBaseCipherLen()), 0);
|
||||
}
|
||||
}
|
||||
TraceEvent("BlobCipherTest_LooksupDone").log();
|
||||
|
@ -548,6 +725,7 @@ TEST_CASE("flow/BlobCipher") {
|
|||
|
||||
// Validate Encyrption ops
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(minDomainId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache.getLatestCipherKey(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
const int bufLen = deterministicRandom()->randomInt(786, 2127) + 512;
|
||||
uint8_t orgData[bufLen];
|
||||
generateRandomData(&orgData[0], bufLen);
|
||||
|
@ -556,68 +734,317 @@ TEST_CASE("flow/BlobCipher") {
|
|||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
generateRandomData(&iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
// validate basic encrypt followed by decrypt operation
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey, iv, AES_256_IV_LENGTH);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
BlobCipherEncryptHeader headerCopy;
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_MODE_NONE
|
||||
{
|
||||
TraceEvent("NoneAuthMode_Start").log();
|
||||
|
||||
ASSERT(encrypted->getLogicalSize() == bufLen);
|
||||
ASSERT(memcmp(&orgData[0], encrypted->begin(), bufLen) != 0);
|
||||
ASSERT(header.flags.headerVersion == EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT(header.flags.encryptMode == BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR);
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
cipherKey, Reference<BlobCipherKey>(), iv, AES_256_IV_LENGTH, ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
||||
TraceEvent("BlobCipherTest_EncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("DomainId", header.encryptDomainId)
|
||||
.detail("BaseCipherId", header.baseCipherId)
|
||||
.detail("HeaderChecksum", header.ciphertextChecksum);
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE);
|
||||
|
||||
Reference<BlobCipherKey> encyrptKey = cipherKeyCache.getCipherKey(header.encryptDomainId, header.baseCipherId);
|
||||
ASSERT(encyrptKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(encyrptKey, &header.iv[0]);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
TraceEvent("BlobCipherTest_EncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId);
|
||||
|
||||
ASSERT(decrypted->getLogicalSize() == bufLen);
|
||||
ASSERT(memcmp(decrypted->begin(), &orgData[0], bufLen) == 0);
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache.getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId);
|
||||
ASSERT(tCipherKeyKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &header.cipherTextDetails.iv[0]);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
|
||||
TraceEvent("BlobCipherTest_DecryptDone").log();
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
header.flags.headerVersion += 1;
|
||||
try {
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
TraceEvent("BlobCipherTest_DecryptDone").log();
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.headerVersion += 1;
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
header.flags.headerVersion -= 1;
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.encryptMode += 1;
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encrypted buffer payload corruption
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
uint8_t temp[bufLen];
|
||||
memcpy(encrypted->begin(), &temp[0], bufLen);
|
||||
int tIdx = deterministicRandom()->randomInt(0, bufLen - 1);
|
||||
temp[tIdx] += 1;
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(&temp[0], bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
// No authToken, hence, no corruption detection supported
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
TraceEvent("NoneAuthMode_Done").log();
|
||||
}
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
header.flags.encryptMode += 1;
|
||||
try {
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_TOKEN_MODE_SINGLE
|
||||
{
|
||||
TraceEvent("SingleAuthMode_Start").log();
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
cipherKey, headerCipherKey, iv, AES_256_IV_LENGTH, ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
|
||||
TraceEvent("BlobCipherTest_EncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString());
|
||||
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache.getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId);
|
||||
Reference<BlobCipherKey> hCipherKey = cipherKeyCache.getCipherKey(header.cipherHeaderDetails.encryptDomainId,
|
||||
header.cipherHeaderDetails.baseCipherId);
|
||||
ASSERT(tCipherKeyKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTest_DecryptDone").log();
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.headerVersion += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
header.flags.encryptMode -= 1;
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.encryptMode += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
headerCopy.singleAuthToken.authToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encrypted buffer payload corruption
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
uint8_t temp[bufLen];
|
||||
memcpy(encrypted->begin(), &temp[0], bufLen);
|
||||
int tIdx = deterministicRandom()->randomInt(0, bufLen - 1);
|
||||
temp[tIdx] += 1;
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(&temp[0], bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("SingleAuthMode_Done").log();
|
||||
}
|
||||
|
||||
// induce encryption header corruption - checksum mismatch
|
||||
header.ciphertextChecksum += 1;
|
||||
try {
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_checksum_mismatch) {
|
||||
throw;
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_TOKEN_MODE_MULTI
|
||||
{
|
||||
TraceEvent("MultiAuthMode_Start").log();
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
cipherKey, headerCipherKey, iv, AES_256_IV_LENGTH, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
|
||||
TraceEvent("BlobCipherTest_EncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString());
|
||||
|
||||
Reference<BlobCipherKey> tCipherKey = cipherKeyCache.getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId);
|
||||
Reference<BlobCipherKey> hCipherKey = cipherKeyCache.getCipherKey(header.cipherHeaderDetails.encryptDomainId,
|
||||
header.cipherHeaderDetails.baseCipherId);
|
||||
|
||||
ASSERT(tCipherKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTest_DecryptDone").log();
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.headerVersion += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
header.ciphertextChecksum -= 1;
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.encryptMode += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - cipherText authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.cipherTextAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - header authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.headerAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
uint8_t temp[bufLen];
|
||||
memcpy(encrypted->begin(), &temp[0], bufLen);
|
||||
int tIdx = deterministicRandom()->randomInt(0, bufLen - 1);
|
||||
temp[tIdx] += 1;
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, &header.cipherTextDetails.iv[0]);
|
||||
decrypted = decryptor.decrypt(&temp[0], bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("MultiAuthMode_Done").log();
|
||||
}
|
||||
|
||||
// Validate dropping encyrptDomainId cached keys
|
||||
const BlobCipherDomainId candidate = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
const EncryptCipherDomainId candidate = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
cipherKeyCache.resetEncyrptDomainId(candidate);
|
||||
std::vector<Reference<BlobCipherKey>> cachedKeys = cipherKeyCache.getAllCiphers(candidate);
|
||||
ASSERT(cachedKeys.empty());
|
||||
|
@ -633,20 +1060,4 @@ TEST_CASE("flow/BlobCipher") {
|
|||
return Void();
|
||||
}
|
||||
|
||||
BlobCipherChecksum computeEncryptChecksum(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
const BlobCipherRandomSalt& salt,
|
||||
Arena& arena) {
|
||||
// FIPS compliance recommendation is to leverage cryptographic digest mechanism to generate checksum
|
||||
// Leverage HMAC_SHA256 using header.randomSalt as the initialization 'key' for the hmac digest.
|
||||
|
||||
HmacSha256DigestGen hmacGenerator((const uint8_t*)&salt, sizeof(salt));
|
||||
StringRef digest = hmacGenerator.digest(payload, payloadLen, arena);
|
||||
ASSERT(digest.size() >= sizeof(BlobCipherChecksum));
|
||||
|
||||
BlobCipherChecksum checksum;
|
||||
memcpy((uint8_t*)&checksum, digest.begin(), sizeof(BlobCipherChecksum));
|
||||
return checksum;
|
||||
}
|
||||
|
||||
#endif // ENCRYPTION_ENABLED
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#if ENCRYPTION_ENABLED
|
||||
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/xxhash.h"
|
||||
|
@ -45,15 +46,6 @@
|
|||
|
||||
#define AES_256_KEY_LENGTH 32
|
||||
#define AES_256_IV_LENGTH 16
|
||||
#define INVALID_DOMAIN_ID 0
|
||||
#define INVALID_CIPHER_KEY_ID 0
|
||||
|
||||
using BlobCipherDomainId = uint64_t;
|
||||
using BlobCipherRandomSalt = uint64_t;
|
||||
using BlobCipherBaseKeyId = uint64_t;
|
||||
using BlobCipherChecksum = uint64_t;
|
||||
|
||||
typedef enum { BLOB_CIPHER_ENCRYPT_MODE_NONE = 0, BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR = 1 } BlockCipherEncryptMode;
|
||||
|
||||
// Encryption operations buffer management
|
||||
// Approach limits number of copies needed during encryption or decryption operations.
|
||||
|
@ -89,51 +81,94 @@ private:
|
|||
// This header is persisted along with encrypted buffer, it contains information necessary
|
||||
// to assist decrypting the buffers to serve read requests.
|
||||
//
|
||||
// The total space overhead is 56 bytes.
|
||||
// The total space overhead is 96 bytes.
|
||||
|
||||
#pragma pack(push, 1) // exact fit - no padding
|
||||
typedef struct BlobCipherEncryptHeader {
|
||||
static constexpr int headerSize = 96;
|
||||
union {
|
||||
struct {
|
||||
uint8_t size; // reading first byte is sufficient to determine header
|
||||
// length. ALWAYS THE FIRST HEADER ELEMENT.
|
||||
uint8_t headerVersion{};
|
||||
uint8_t encryptMode{};
|
||||
uint8_t _reserved[5]{};
|
||||
uint8_t authTokenMode{};
|
||||
uint8_t _reserved[4]{};
|
||||
} flags;
|
||||
uint64_t _padding{};
|
||||
};
|
||||
// Encyrption domain boundary identifier.
|
||||
BlobCipherDomainId encryptDomainId{};
|
||||
// BaseCipher encryption key identifier
|
||||
BlobCipherBaseKeyId baseCipherId{};
|
||||
// Random salt
|
||||
BlobCipherRandomSalt salt{};
|
||||
// Checksum of the encrypted buffer. It protects against 'tampering' of ciphertext as well 'bit rots/flips'.
|
||||
BlobCipherChecksum ciphertextChecksum{};
|
||||
// Initialization vector used to encrypt the payload.
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
|
||||
BlobCipherEncryptHeader();
|
||||
// Cipher text encryption information
|
||||
struct {
|
||||
// Encyrption domain boundary identifier.
|
||||
EncryptCipherDomainId encryptDomainId{};
|
||||
// BaseCipher encryption key identifier
|
||||
EncryptCipherBaseKeyId baseCipherId{};
|
||||
// Random salt
|
||||
EncryptCipherRandomSalt salt{};
|
||||
// Initialization vector used to encrypt the payload.
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
} cipherTextDetails;
|
||||
|
||||
struct {
|
||||
// Encryption domainId for the header
|
||||
EncryptCipherDomainId encryptDomainId{};
|
||||
// BaseCipher encryption key identifier.
|
||||
EncryptCipherBaseKeyId baseCipherId{};
|
||||
} cipherHeaderDetails;
|
||||
|
||||
// Encryption header is stored as plaintext on a persistent storage to assist reconstruction of cipher-key(s) for
|
||||
// reads. FIPS compliance recommendation is to leverage cryptographic digest mechanism to generate 'authentication
|
||||
// token' (crypto-secure) to protect against malicious tampering and/or bit rot/flip scenarios.
|
||||
|
||||
union {
|
||||
// Encryption header support two modes of generation 'authentication tokens':
|
||||
// 1) SingleAuthTokenMode: the scheme generates single crypto-secrure auth token to protect {cipherText +
|
||||
// header} payload. Scheme is geared towards optimizing cost due to crypto-secure auth-token generation,
|
||||
// however, on decryption client needs to be read 'header' + 'encrypted-buffer' to validate the 'auth-token'.
|
||||
// The scheme is ideal for usecases where payload represented by the encryptionHeader is not large and it is
|
||||
// desirable to minimize CPU/latency penalty due to crypto-secure ops, such as: CommitProxies encrypted inline
|
||||
// transactions, StorageServer encrypting pages etc. 2) MultiAuthTokenMode: Scheme generates separate authTokens
|
||||
// for 'encrypted buffer' & 'encryption-header'. The scheme is ideal where payload represented by
|
||||
// encryptionHeader is large enough such that it is desirable to optimize cost of upfront reading full
|
||||
// 'encrypted buffer', compared to reading only encryptionHeader and ensuring its sanity; for instance:
|
||||
// backup-files.
|
||||
|
||||
struct {
|
||||
// Cipher text authentication token
|
||||
uint8_t cipherTextAuthToken[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t headerAuthToken[AUTH_TOKEN_SIZE]{};
|
||||
} multiAuthTokens;
|
||||
struct {
|
||||
uint8_t authToken[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t _reserved[AUTH_TOKEN_SIZE]{};
|
||||
} singleAuthToken;
|
||||
};
|
||||
|
||||
BlobCipherEncryptHeader() {}
|
||||
} BlobCipherEncryptHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
// Ensure no struct-packing issues
|
||||
static_assert(sizeof(BlobCipherEncryptHeader) == BlobCipherEncryptHeader::headerSize,
|
||||
"BlobCipherEncryptHeader size mismatch");
|
||||
|
||||
// This interface is in-memory representation of CipherKey used for encryption/decryption information.
|
||||
// It caches base encryption key properties as well as caches the 'derived encryption' key obtained by applying
|
||||
// HMAC-SHA-256 derivation technique.
|
||||
|
||||
class BlobCipherKey : public ReferenceCounted<BlobCipherKey>, NonCopyable {
|
||||
public:
|
||||
BlobCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
BlobCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCiphId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen);
|
||||
|
||||
uint8_t* data() const { return cipher.get(); }
|
||||
uint64_t getCreationTime() const { return creationTime; }
|
||||
BlobCipherDomainId getDomainId() const { return encryptDomainId; }
|
||||
BlobCipherRandomSalt getSalt() const { return randomSalt; }
|
||||
BlobCipherBaseKeyId getBaseCipherId() const { return baseCipherId; }
|
||||
EncryptCipherDomainId getDomainId() const { return encryptDomainId; }
|
||||
EncryptCipherRandomSalt getSalt() const { return randomSalt; }
|
||||
EncryptCipherBaseKeyId getBaseCipherId() const { return baseCipherId; }
|
||||
int getBaseCipherLen() const { return baseCipherLen; }
|
||||
uint8_t* rawCipher() const { return cipher.get(); }
|
||||
uint8_t* rawBaseCipher() const { return baseCipher.get(); }
|
||||
|
@ -147,23 +182,23 @@ public:
|
|||
|
||||
private:
|
||||
// Encryption domain boundary identifier
|
||||
BlobCipherDomainId encryptDomainId;
|
||||
EncryptCipherDomainId encryptDomainId;
|
||||
// Base encryption cipher key properties
|
||||
std::unique_ptr<uint8_t[]> baseCipher;
|
||||
int baseCipherLen;
|
||||
BlobCipherBaseKeyId baseCipherId;
|
||||
EncryptCipherBaseKeyId baseCipherId;
|
||||
// Random salt used for encryption cipher key derivation
|
||||
BlobCipherRandomSalt randomSalt;
|
||||
EncryptCipherRandomSalt randomSalt;
|
||||
// Creation timestamp for the derived encryption cipher key
|
||||
uint64_t creationTime;
|
||||
// Derived encryption cipher key
|
||||
std::unique_ptr<uint8_t[]> cipher;
|
||||
|
||||
void initKey(const BlobCipherDomainId& domainId,
|
||||
void initKey(const EncryptCipherDomainId& domainId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
const BlobCipherRandomSalt& salt);
|
||||
const EncryptCipherBaseKeyId& baseCiphId,
|
||||
const EncryptCipherRandomSalt& salt);
|
||||
void applyHmacSha256Derivation();
|
||||
};
|
||||
|
||||
|
@ -190,37 +225,45 @@ private:
|
|||
// required encryption key, however, CPs/SSs cache-miss would result in RPC to
|
||||
// EncryptKeyServer to refresh the desired encryption key.
|
||||
|
||||
using BlobCipherKeyIdCacheMap = std::unordered_map<BlobCipherBaseKeyId, Reference<BlobCipherKey>>;
|
||||
using BlobCipherKeyIdCacheMapCItr = std::unordered_map<BlobCipherBaseKeyId, Reference<BlobCipherKey>>::const_iterator;
|
||||
using BlobCipherKeyIdCacheMap = std::unordered_map<EncryptCipherBaseKeyId, Reference<BlobCipherKey>>;
|
||||
using BlobCipherKeyIdCacheMapCItr =
|
||||
std::unordered_map<EncryptCipherBaseKeyId, Reference<BlobCipherKey>>::const_iterator;
|
||||
|
||||
struct BlobCipherKeyIdCache : ReferenceCounted<BlobCipherKeyIdCache> {
|
||||
public:
|
||||
BlobCipherKeyIdCache();
|
||||
explicit BlobCipherKeyIdCache(BlobCipherDomainId dId);
|
||||
explicit BlobCipherKeyIdCache(EncryptCipherDomainId dId);
|
||||
|
||||
// API returns the last inserted cipherKey.
|
||||
// If none exists, 'encrypt_key_not_found' is thrown.
|
||||
|
||||
Reference<BlobCipherKey> getLatestCipherKey();
|
||||
|
||||
// API returns cipherKey corresponding to input 'baseCipherKeyId'.
|
||||
// If none exists, 'encrypt_key_not_found' is thrown.
|
||||
Reference<BlobCipherKey> getCipherByBaseCipherId(BlobCipherBaseKeyId baseCipherKeyId);
|
||||
|
||||
Reference<BlobCipherKey> getCipherByBaseCipherId(EncryptCipherBaseKeyId baseCipherKeyId);
|
||||
|
||||
// API enables inserting base encryption cipher details to the BlobCipherKeyIdCache.
|
||||
// Given cipherKeys are immutable, attempting to re-insert same 'identical' cipherKey
|
||||
// is treated as a NOP (success), however, an attempt to update cipherKey would throw
|
||||
// 'encrypt_update_cipher' exception.
|
||||
void insertBaseCipherKey(BlobCipherBaseKeyId baseCipherId, const uint8_t* baseCipher, int baseCipherLen);
|
||||
|
||||
void insertBaseCipherKey(EncryptCipherBaseKeyId baseCipherId, const uint8_t* baseCipher, int baseCipherLen);
|
||||
|
||||
// API cleanup the cache by dropping all cached cipherKeys
|
||||
void cleanup();
|
||||
|
||||
// API returns list of all 'cached' cipherKeys
|
||||
std::vector<Reference<BlobCipherKey>> getAllCipherKeys();
|
||||
|
||||
private:
|
||||
BlobCipherDomainId domainId;
|
||||
EncryptCipherDomainId domainId;
|
||||
BlobCipherKeyIdCacheMap keyIdCache;
|
||||
BlobCipherBaseKeyId latestBaseCipherKeyId;
|
||||
EncryptCipherBaseKeyId latestBaseCipherKeyId;
|
||||
};
|
||||
|
||||
using BlobCipherDomainCacheMap = std::unordered_map<BlobCipherDomainId, Reference<BlobCipherKeyIdCache>>;
|
||||
using BlobCipherDomainCacheMap = std::unordered_map<EncryptCipherDomainId, Reference<BlobCipherKeyIdCache>>;
|
||||
|
||||
class BlobCipherKeyCache : NonCopyable {
|
||||
public:
|
||||
|
@ -228,21 +271,28 @@ public:
|
|||
// The cipherKeys are indexed using 'baseCipherId', given cipherKeys are immutable,
|
||||
// attempting to re-insert same 'identical' cipherKey is treated as a NOP (success),
|
||||
// however, an attempt to update cipherKey would throw 'encrypt_update_cipher' exception.
|
||||
void insertCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCipherId,
|
||||
|
||||
void insertCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCipherId,
|
||||
const uint8_t* baseCipher,
|
||||
int baseCipherLen);
|
||||
// API returns the last insert cipherKey for a given encyryption domain Id.
|
||||
// If none exists, it would throw 'encrypt_key_not_found' exception.
|
||||
Reference<BlobCipherKey> getLatestCipherKey(const BlobCipherDomainId& domainId);
|
||||
|
||||
Reference<BlobCipherKey> getLatestCipherKey(const EncryptCipherDomainId& domainId);
|
||||
|
||||
// API returns cipherKey corresponding to {encryptionDomainId, baseCipherId} tuple.
|
||||
// If none exists, it would throw 'encrypt_key_not_found' exception.
|
||||
Reference<BlobCipherKey> getCipherKey(const BlobCipherDomainId& domainId, const BlobCipherBaseKeyId& baseCipherId);
|
||||
|
||||
Reference<BlobCipherKey> getCipherKey(const EncryptCipherDomainId& domainId,
|
||||
const EncryptCipherBaseKeyId& baseCipherId);
|
||||
// API returns point in time list of all 'cached' cipherKeys for a given encryption domainId.
|
||||
std::vector<Reference<BlobCipherKey>> getAllCiphers(const BlobCipherDomainId& domainId);
|
||||
std::vector<Reference<BlobCipherKey>> getAllCiphers(const EncryptCipherDomainId& domainId);
|
||||
|
||||
// API enables dropping all 'cached' cipherKeys for a given encryption domain Id.
|
||||
// Useful to cleanup cache if an encryption domain gets removed/destroyed etc.
|
||||
void resetEncyrptDomainId(const BlobCipherDomainId domainId);
|
||||
|
||||
void resetEncyrptDomainId(const EncryptCipherDomainId domainId);
|
||||
|
||||
static BlobCipherKeyCache& getInstance() {
|
||||
static BlobCipherKeyCache instance;
|
||||
|
@ -262,14 +312,19 @@ private:
|
|||
// This interface enables data block encryption. An invocation to encrypt() will
|
||||
// do two things:
|
||||
// 1) generate encrypted ciphertext for given plaintext input.
|
||||
// 2) generate BlobCipherEncryptHeader (including the 'header checksum') and persit for decryption on reads.
|
||||
// 2) generate BlobCipherEncryptHeader (including the 'header authTokens') and persit for decryption on reads.
|
||||
|
||||
class EncryptBlobCipherAes265Ctr final : NonCopyable, public ReferenceCounted<EncryptBlobCipherAes265Ctr> {
|
||||
public:
|
||||
static constexpr uint8_t ENCRYPT_HEADER_VERSION = 1;
|
||||
|
||||
EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> key, const uint8_t* iv, const int ivLen);
|
||||
EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* iv,
|
||||
const int ivLen,
|
||||
const EncryptAuthTokenMode mode);
|
||||
~EncryptBlobCipherAes265Ctr();
|
||||
|
||||
Reference<EncryptBuf> encrypt(const uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeader* header,
|
||||
|
@ -277,7 +332,9 @@ public:
|
|||
|
||||
private:
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
Reference<BlobCipherKey> cipherKey;
|
||||
Reference<BlobCipherKey> textCipherKey;
|
||||
Reference<BlobCipherKey> headerCipherKey;
|
||||
EncryptAuthTokenMode authTokenMode;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
};
|
||||
|
||||
|
@ -286,20 +343,44 @@ private:
|
|||
|
||||
class DecryptBlobCipherAes256Ctr final : NonCopyable, public ReferenceCounted<DecryptBlobCipherAes256Ctr> {
|
||||
public:
|
||||
DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> key, const uint8_t* iv);
|
||||
DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* iv);
|
||||
~DecryptBlobCipherAes256Ctr();
|
||||
|
||||
Reference<EncryptBuf> decrypt(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena&);
|
||||
|
||||
// Enable caller to validate encryption header auth-token (if available) without needing to read the full encyrpted
|
||||
// payload. The call is NOP unless header.flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI.
|
||||
|
||||
void verifyHeaderAuthToken(const BlobCipherEncryptHeader& header, Arena& arena);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
Reference<BlobCipherKey> textCipherKey;
|
||||
Reference<BlobCipherKey> headerCipherKey;
|
||||
bool headerAuthTokenValidationDone;
|
||||
bool authTokensValidationDone;
|
||||
|
||||
void verifyEncryptBlobHeader(const uint8_t* cipherText,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena& arena);
|
||||
void verifyEncryptHeaderMetadata(const BlobCipherEncryptHeader& header);
|
||||
void verifyAuthTokens(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
void verifyHeaderSingleAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
void verifyHeaderMultiAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
};
|
||||
|
||||
class HmacSha256DigestGen final : NonCopyable {
|
||||
|
@ -313,9 +394,10 @@ private:
|
|||
HMAC_CTX* ctx;
|
||||
};
|
||||
|
||||
BlobCipherChecksum computeEncryptChecksum(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
const BlobCipherRandomSalt& salt,
|
||||
Arena& arena);
|
||||
StringRef computeAuthToken(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
const uint8_t* key,
|
||||
const int keyLen,
|
||||
Arena& arena);
|
||||
|
||||
#endif // ENCRYPTION_ENABLED
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* EncryptUtils.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ENCRYPT_UTILS_H
|
||||
#define ENCRYPT_UTILS_H
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#define ENCRYPT_INVALID_DOMAIN_ID 0
|
||||
#define ENCRYPT_INVALID_CIPHER_KEY_ID 0
|
||||
|
||||
#define AUTH_TOKEN_SIZE 16
|
||||
|
||||
#define SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID -1
|
||||
#define ENCRYPT_HEADER_DOMAIN_ID -2
|
||||
|
||||
using EncryptCipherDomainId = int64_t;
|
||||
using EncryptCipherBaseKeyId = uint64_t;
|
||||
using EncryptCipherRandomSalt = uint64_t;
|
||||
|
||||
typedef enum {
|
||||
ENCRYPT_CIPHER_MODE_NONE = 0,
|
||||
ENCRYPT_CIPHER_MODE_AES_256_CTR = 1,
|
||||
ENCRYPT_CIPHER_MODE_LAST = 2
|
||||
} EncryptCipherMode;
|
||||
|
||||
static_assert(EncryptCipherMode::ENCRYPT_CIPHER_MODE_LAST <= std::numeric_limits<uint8_t>::max(),
|
||||
"EncryptCipherMode value overflow");
|
||||
|
||||
// EncryptionHeader authentication modes
|
||||
// 1. NONE - No 'authentication token' generation needed for EncryptionHeader i.e. no protection against header OR
|
||||
// cipherText 'tampering' and/or bit rot/flip corruptions.
|
||||
// 2. Single/Multi - Encyrption header would generate one or more 'authentication tokens' to protect the header against
|
||||
// 'tempering' and/or bit rot/flip corruptions. Refer to BlobCipher.h for detailed usage recommendations.
|
||||
// 3. LAST - Invalid mode, used for static asserts.
|
||||
|
||||
typedef enum {
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE = 0,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE = 1,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI = 2,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_LAST = 3 // Always the last element
|
||||
} EncryptAuthTokenMode;
|
||||
|
||||
static_assert(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_LAST <= std::numeric_limits<uint8_t>::max(),
|
||||
"EncryptHeaderAuthToken value overflow");
|
||||
|
||||
#endif
|
|
@ -73,3 +73,11 @@ T waitNext(const FutureStream<T>&);
|
|||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4355) // 'this' : used in base member initializer list
|
||||
#endif
|
||||
|
||||
// Currently, #ifdef can't be used inside actors, so define no-op versions of these valgrind
|
||||
// functions if valgrind is not defined
|
||||
#ifndef VALGRIND
|
||||
#define VALGRIND_MAKE_MEM_UNDEFINED(x, y)
|
||||
#define VALGRIND_MAKE_MEM_DEFINED(x, y)
|
||||
#define VALGRIND_CHECK_MEM_IS_DEFINED(x, y) 0
|
||||
#endif
|
||||
|
|
|
@ -88,7 +88,14 @@ ERROR( blob_granule_transaction_too_old, 1064, "Read version is older than blob
|
|||
ERROR( blob_manager_replaced, 1065, "This blob manager has been replaced." )
|
||||
ERROR( change_feed_popped, 1066, "Tried to read a version older than what has been popped from the change feed" )
|
||||
ERROR( remote_kvs_cancelled, 1067, "The remote key-value store is cancelled" )
|
||||
ERROR( stale_version_vector, 1068, "Client version vector is stale" )
|
||||
ERROR( page_header_wrong_page_id, 1068, "Page header does not match location on disk" )
|
||||
ERROR( page_header_checksum_failed, 1069, "Page header checksum failed" )
|
||||
ERROR( page_header_version_not_supported, 1070, "Page header version is not supported" )
|
||||
ERROR( page_encoding_not_supported, 1071, "Page encoding type is not supported or not valid" )
|
||||
ERROR( page_decoding_failed, 1072, "Page content decoding failed" )
|
||||
ERROR( unexpected_encoding_type, 1073, "Page content decoding failed" )
|
||||
ERROR( encryption_key_not_found, 1074, "Encryption key not found" )
|
||||
ERROR( stale_version_vector, 1075, "Client version vector is stale" )
|
||||
|
||||
ERROR( broken_promise, 1100, "Broken promise" )
|
||||
ERROR( operation_cancelled, 1101, "Asynchronous operation cancelled" )
|
||||
|
@ -291,14 +298,14 @@ ERROR( snap_log_anti_quorum_unsupported, 2507, "Unsupported when log anti quorum
|
|||
ERROR( snap_with_recovery_unsupported, 2508, "Cluster recovery during snapshot operation not supported")
|
||||
ERROR( snap_invalid_uid_string, 2509, "The given uid string is not a 32-length hex string")
|
||||
|
||||
// 3XXX - Encryption operations errors
|
||||
ERROR( encrypt_ops_error, 3000, "Encryption operation error")
|
||||
ERROR( encrypt_header_metadata_mismatch, 3001, "Encryption header metadata mismatch")
|
||||
ERROR( encrypt_key_not_found, 3002, "Expected encryption key is missing")
|
||||
ERROR( encrypt_key_ttl_expired, 3003, "Expected encryption key TTL has expired")
|
||||
ERROR( encrypt_header_checksum_mismatch, 3004, "Encryption header checksum mismatch")
|
||||
ERROR( encrypt_update_cipher, 3005, "Attempt to update encryption cipher key")
|
||||
ERROR( encrypt_invalid_id, 3006, "Invalid encryption domainId or encryption cipher key id")
|
||||
// 27XX - Encryption operations errors
|
||||
ERROR( encrypt_ops_error, 2700, "Encryption operation error")
|
||||
ERROR( encrypt_header_metadata_mismatch, 2701, "Encryption header metadata mismatch")
|
||||
ERROR( encrypt_key_not_found, 2702, "Expected encryption key is missing")
|
||||
ERROR( encrypt_key_ttl_expired, 2703, "Expected encryption key TTL has expired")
|
||||
ERROR( encrypt_header_authtoken_mismatch, 2704, "Encryption header authentication token mismatch")
|
||||
ERROR( encrypt_update_cipher, 2705, "Attempt to update encryption cipher key")
|
||||
ERROR( encrypt_invalid_id, 2706, "Invalid encryption domainId or encryption cipher key id")
|
||||
|
||||
// 4xxx Internal errors (those that should be generated only by bugs) are decimal 4xxx
|
||||
ERROR( unknown_error, 4000, "An unknown error occurred" ) // C++ exception not of type Error
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storageEngineExcludeTypes=3
|
||||
|
||||
logAntiQuorum = 0
|
||||
|
||||
testTitle=SubmitBackup
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storageEngineExcludeTypes=3
|
||||
|
||||
;write 1000 Keys ending with even numbers
|
||||
testTitle=SnapTestPre
|
||||
clearAfterTest=false
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storageEngineExcludeTypes=3
|
||||
|
||||
;write 1000 Keys ending with even numbers
|
||||
testTitle=SnapTestPre
|
||||
clearAfterTest=false
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storageEngineExcludeTypes=3
|
||||
|
||||
;write 1000 Keys ending with even number
|
||||
testTitle=SnapSimplePre
|
||||
clearAfterTest=false
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storageEngineExcludeTypes=3
|
||||
|
||||
[[test]]
|
||||
testTitle = 'SubmitBackup'
|
||||
simBackupAgents= 'BackupToFile'
|
||||
|
|
|
@ -10,6 +10,7 @@ waitForQuiescenceBegin=false
|
|||
testName = 'ConfigureDatabase'
|
||||
testDuration = 300.0
|
||||
waitStoreTypeCheck = true
|
||||
storageMigrationCompatibleConf = true
|
||||
|
||||
[[test.workload]]
|
||||
testName = 'RandomClogging'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
storageEngineExcludeTypes=-1,-2
|
||||
storageEngineExcludeTypes=-1,-2,3
|
||||
maxTLogVersion=6
|
||||
disableTss=true
|
||||
disableHostname=true
|
||||
|
|
|
@ -10,6 +10,7 @@ waitForQuiescenceBegin=false
|
|||
testName = 'ConfigureDatabase'
|
||||
testDuration = 300.0
|
||||
waitStoreTypeCheck = true
|
||||
storageMigrationCompatibleConf = true
|
||||
|
||||
[[test.workload]]
|
||||
testName = 'RandomClogging'
|
||||
|
|
Loading…
Reference in New Issue