Introduce BlobCipher interface and cipher caching interface (#6391)
* Introduce BlobCipher interface and cipher caching interface diff-3: Update the code to avoid deriving encryption key periodically. Implement EncyrptBuf interface to limit memcpys. Improve both unit test and simulation to better code coverage. diff-2: Add specific error code for OpenSSL AES call failures diff-1: Update encryption scheme to AES-256-CTR. Minor updates to Header to capture more information. Major changes proposed are: 1. Introduce encyrption header format. 2. Introduce a BlobCipher cipher key representation encoding following information: baseCipher details, derived encryption cipher details, creationTime and random salt. 3. Introduce interface to support block cipher encrytion and decrytion operations. Encyrption populates encryption header allowing client to persist them on-disk, this header is then read allowing decryption on reads. 4. Introduce interface to allow in-memory caching of cipher keys. The cache allowing mapping of "encryption domain" -> "base cipher id" -> "derived cipher keys" (3D hash map). This cache interface will be used by FDB processes participating in encryption to cache recently used ciphers (performance optimization). Testing: 1. Unit test to validate caching interface. 2. Update EncryptionOps simulation test to validate block cipher operations.
This commit is contained in:
parent
977fab2089
commit
017709aec6
|
@ -828,6 +828,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
|
||||
// encrypt key proxy
|
||||
init( ENABLE_ENCRYPTION, false );
|
||||
init( ENCRYPTION_MODE, "AES-256-CTR");
|
||||
|
||||
// Blob granlues
|
||||
init( BG_URL, isSimulated ? "file://fdbblob/" : "" ); // TODO: store in system key space or something, eventually
|
||||
|
|
|
@ -775,8 +775,9 @@ public:
|
|||
// Cluster recovery
|
||||
std::string CLUSTER_RECOVERY_EVENT_NAME_PREFIX;
|
||||
|
||||
// encrypt key proxy
|
||||
// Encryption
|
||||
bool ENABLE_ENCRYPTION;
|
||||
std::string ENCRYPTION_MODE;
|
||||
|
||||
// blob granule stuff
|
||||
// FIXME: configure url with database configuration instead of knob eventually
|
||||
|
|
|
@ -2378,9 +2378,9 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
(!nonExcludedWorkerProcessMap.count(db.encryptKeyProxy.get().address()) ||
|
||||
nonExcludedWorkerProcessMap[db.encryptKeyProxy.get().address()].processClass.machineClassFitness(
|
||||
ProcessClass::EncryptKeyProxy) > fitnessLowerBound)) {
|
||||
TraceEvent("ConsistencyCheck_EncyrptKeyProxyNotBest")
|
||||
TraceEvent("ConsistencyCheck_EncryptKeyProxyNotBest")
|
||||
.detail("BestEncryptKeyProxyFitness", fitnessLowerBound)
|
||||
.detail("ExistingEncyrptKeyProxyFitness",
|
||||
.detail("ExistingEncryptKeyProxyFitness",
|
||||
nonExcludedWorkerProcessMap.count(db.encryptKeyProxy.get().address())
|
||||
? nonExcludedWorkerProcessMap[db.encryptKeyProxy.get().address()]
|
||||
.processClass.machineClassFitness(ProcessClass::EncryptKeyProxy)
|
||||
|
|
|
@ -21,16 +21,19 @@
|
|||
#include "fdbclient/DatabaseContext.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/StreamCipher.h"
|
||||
#include "flow/BlobCipher.h"
|
||||
#include "fdbserver/workloads/workloads.actor.h"
|
||||
#include "flow/ITrace.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
#if ENCRYPTION_ENABLED
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
#if ENCRYPTION_ENABLED
|
||||
|
||||
#define MEGA_BYTES (1024 * 1024)
|
||||
#define NANO_SECOND (1000 * 1000 * 1000)
|
||||
|
@ -78,45 +81,69 @@ struct WorkloadMetrics {
|
|||
}
|
||||
};
|
||||
|
||||
// Workload generator for encryption/decryption operations.
|
||||
// 1. For every client run, it generate unique random encryptionDomainId range and simulate encryption of
|
||||
// either fixed size or variable size payload.
|
||||
// 2. For each encryption run, it would interact with BlobCipherKeyCache to fetch the desired encryption key,
|
||||
// which then is used for encrypting the plaintext payload.
|
||||
// 3. Encryption operation generates 'encryption header', it is leveraged to decrypt the ciphertext obtained from
|
||||
// step#2 (simulate real-world scenario)
|
||||
//
|
||||
// Correctness validations:
|
||||
// -----------------------
|
||||
// Correctness invariants are validated at various steps:
|
||||
// 1. Encryption key correctness: as part of performing decryption, BlobCipherKeyCache lookup is done to procure
|
||||
// desired encrytion key based on: {encryptionDomainId, baseCipherId}; the obtained key is validated against
|
||||
// the encryption key used for encrypting the data.
|
||||
// 2. After encryption, generated 'encryption header' fields are validated, encrypted buffer size and contents are
|
||||
// validated.
|
||||
// 3. After decryption, the obtained deciphertext is validated against the orginal plaintext payload.
|
||||
//
|
||||
// Performance metrics:
|
||||
// -------------------
|
||||
// The workload generator profiles below operations across the iterations and logs the details at the end, they are:
|
||||
// 1. Time spent in encryption key fetch (and derivation) operations.
|
||||
// 2. Time spent encrypting the buffer (doesn't incude key lookup time); also records the throughput in MB/sec.
|
||||
// 3. Time spent decrypting the buffer (doesn't incude key lookup time); also records the throughput in MB/sec.
|
||||
|
||||
struct EncryptionOpsWorkload : TestWorkload {
|
||||
int mode;
|
||||
int64_t numIterations;
|
||||
int pageSize;
|
||||
int maxBufSize;
|
||||
std::unique_ptr<uint8_t[]> buff;
|
||||
std::unique_ptr<uint8_t[]> validationBuff;
|
||||
|
||||
StreamCipher::IV iv;
|
||||
std::unique_ptr<HmacSha256StreamCipher> hmacGenerator;
|
||||
std::unique_ptr<uint8_t[]> parentKey;
|
||||
Arena arena;
|
||||
std::unique_ptr<WorkloadMetrics> metrics;
|
||||
|
||||
BlobCipherDomainId minDomainId;
|
||||
BlobCipherDomainId maxDomainId;
|
||||
BlobCipherBaseKeyId minBaseCipherId;
|
||||
|
||||
EncryptionOpsWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
|
||||
mode = getOption(options, LiteralStringRef("fixedSize"), 1);
|
||||
numIterations = getOption(options, LiteralStringRef("numIterations"), 10);
|
||||
pageSize = getOption(options, LiteralStringRef("pageSize"), 4096);
|
||||
maxBufSize = getOption(options, LiteralStringRef("maxBufSize"), 512 * 1024);
|
||||
buff = std::make_unique<uint8_t[]>(maxBufSize);
|
||||
validationBuff = std::make_unique<uint8_t[]>(maxBufSize);
|
||||
|
||||
iv = getRandomIV();
|
||||
hmacGenerator = std::make_unique<HmacSha256StreamCipher>();
|
||||
parentKey = std::make_unique<uint8_t[]>(AES_256_KEY_LENGTH);
|
||||
generateRandomData(parentKey.get(), AES_256_KEY_LENGTH);
|
||||
// assign unique encryptionDomainId range per workload clients
|
||||
minDomainId = wcx.clientId * 100 + mode * 30 + 1;
|
||||
maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 10) + 5;
|
||||
minBaseCipherId = 100;
|
||||
|
||||
metrics = std::make_unique<WorkloadMetrics>();
|
||||
|
||||
TraceEvent("EncryptionOpsWorkload").detail("Mode", getModeStr());
|
||||
TraceEvent("EncryptionOpsWorkload")
|
||||
.detail("Mode", getModeStr())
|
||||
.detail("MinDomainId", minDomainId)
|
||||
.detail("MaxDomainId", maxDomainId);
|
||||
}
|
||||
|
||||
~EncryptionOpsWorkload() { TraceEvent("EncryptionOpsWorkload_Done").log(); }
|
||||
|
||||
bool isFixedSizePayload() { return mode == 1; }
|
||||
|
||||
StreamCipher::IV getRandomIV() {
|
||||
generateRandomData(iv.data(), iv.size());
|
||||
return iv;
|
||||
}
|
||||
|
||||
std::string getModeStr() const {
|
||||
if (mode == 1) {
|
||||
return "FixedSize";
|
||||
|
@ -127,47 +154,97 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
throw internal_error();
|
||||
}
|
||||
|
||||
void updateEncryptionKey(StreamCipherKey* cipherKey) {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
applyHmacKeyDerivationFunc(cipherKey, hmacGenerator.get(), arena);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
||||
metrics->updateKeyDerivationTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
void generateRandomBaseCipher(const int maxLen, uint8_t* buff, int* retLen) {
|
||||
memset(buff, 0, maxLen);
|
||||
*retLen = deterministicRandom()->randomInt(maxLen / 2, maxLen);
|
||||
generateRandomData(buff, *retLen);
|
||||
}
|
||||
|
||||
StringRef doEncryption(const StreamCipherKey* key, uint8_t* payload, int len) {
|
||||
EncryptionStreamCipher encryptor(key, iv);
|
||||
void setupCipherEssentials() {
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
TraceEvent("SetupCipherEssentials_Start").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
|
||||
uint8_t buff[AES_256_KEY_LENGTH];
|
||||
std::vector<Reference<BlobCipherKey>> cipherKeys;
|
||||
for (BlobCipherDomainId id = minDomainId; id <= maxDomainId; id++) {
|
||||
int cipherLen = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
TraceEvent("SetupCipherEssentials_Done").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
}
|
||||
|
||||
void resetCipherEssentials() {
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
cipherKeyCache.cleanup();
|
||||
|
||||
TraceEvent("ResetCipherEssentials_Done").log();
|
||||
}
|
||||
|
||||
void updateLatestBaseCipher(const BlobCipherDomainId encryptDomainId,
|
||||
uint8_t* baseCipher,
|
||||
int* baseCipherLen,
|
||||
BlobCipherBaseKeyId* nextBaseCipherId) {
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(encryptDomainId);
|
||||
*nextBaseCipherId = cipherKey->getBaseCipherId() + 1;
|
||||
|
||||
generateRandomBaseCipher(AES_256_KEY_LENGTH, baseCipher, baseCipherLen);
|
||||
|
||||
ASSERT(*baseCipherLen > 0 && *baseCipherLen <= AES_256_KEY_LENGTH);
|
||||
TraceEvent("UpdateBaseCipher").detail("DomainId", encryptDomainId).detail("BaseCipherId", *nextBaseCipherId);
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> doEncryption(Reference<BlobCipherKey> key,
|
||||
uint8_t* payload,
|
||||
int len,
|
||||
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);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto encrypted = encryptor.encrypt(buff.get(), len, arena);
|
||||
encryptor.finish(arena);
|
||||
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.size() == len);
|
||||
std::copy(encrypted.begin(), encrypted.end(), validationBuff.get());
|
||||
ASSERT(memcmp(validationBuff.get(), buff.get(), len) != 0);
|
||||
ASSERT(encrypted->getLogicalSize() == len);
|
||||
ASSERT(memcmp(encrypted->begin(), payload, len) != 0);
|
||||
ASSERT(header->flags.headerVersion == EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
|
||||
metrics->updateEncryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
void doDecryption(const StreamCipherKey* key,
|
||||
const StringRef& encrypted,
|
||||
void doDecryption(Reference<EncryptBuf> encrypted,
|
||||
int len,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* originalPayload,
|
||||
uint8_t* validationBuff) {
|
||||
DecryptionStreamCipher decryptor(key, iv);
|
||||
Reference<BlobCipherKey> orgCipherKey) {
|
||||
ASSERT(header.flags.headerVersion == EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT(header.flags.encryptMode == BLOB_CIPHER_ENCRYPT_MODE_AES_256_CTR);
|
||||
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getCipherKey(header.encryptDomainId, header.baseCipherId);
|
||||
ASSERT(cipherKey.isValid());
|
||||
ASSERT(cipherKey->isEqual(orgCipherKey));
|
||||
|
||||
DecryptBlobCipherAes256Ctr decryptor(cipherKey, &header.iv[0]);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Standalone<StringRef> decrypted = decryptor.decrypt(encrypted.begin(), len, arena);
|
||||
decryptor.finish(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.size() == len);
|
||||
std::copy(decrypted.begin(), decrypted.end(), validationBuff);
|
||||
ASSERT(memcmp(validationBuff, originalPayload, len) == 0);
|
||||
ASSERT(decrypted->getLogicalSize() == len);
|
||||
ASSERT(memcmp(decrypted->begin(), originalPayload, len) == 0);
|
||||
|
||||
metrics->updateDecryptionTime(std::chrono::duration<double, std::nano>(end - start).count());
|
||||
}
|
||||
|
@ -177,22 +254,64 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
std::string description() const override { return "EncryptionOps"; }
|
||||
|
||||
Future<Void> start(Database const& cx) override {
|
||||
uint8_t baseCipher[AES_256_KEY_LENGTH];
|
||||
int baseCipherLen = 0;
|
||||
BlobCipherBaseKeyId nextBaseCipherId;
|
||||
|
||||
// Setup encryptDomainIds and corresponding baseCipher details
|
||||
setupCipherEssentials();
|
||||
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
StreamCipherKey key(AES_256_KEY_LENGTH);
|
||||
// derive the encryption key
|
||||
updateEncryptionKey(&key);
|
||||
bool updateBaseCipher = deterministicRandom()->randomInt(1, 100) < 5;
|
||||
|
||||
// Step-1: Encryption key derivation, caching the cipher for later use
|
||||
auto& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
// randomly select a domainId
|
||||
const BlobCipherDomainId encryptDomainId = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
ASSERT(encryptDomainId >= minDomainId && encryptDomainId <= maxDomainId);
|
||||
|
||||
if (updateBaseCipher) {
|
||||
// simulate baseCipherId getting refreshed/updated
|
||||
updateLatestBaseCipher(encryptDomainId, &baseCipher[0], &baseCipherLen, &nextBaseCipherId);
|
||||
cipherKeyCache.insertCipherKey(encryptDomainId, nextBaseCipherId, &baseCipher[0], baseCipherLen);
|
||||
}
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(encryptDomainId);
|
||||
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);
|
||||
}
|
||||
|
||||
int dataLen = isFixedSizePayload() ? pageSize : deterministicRandom()->randomInt(100, maxBufSize);
|
||||
generateRandomData(buff.get(), dataLen);
|
||||
|
||||
// encrypt the payload
|
||||
const auto& encrypted = doEncryption(&key, buff.get(), dataLen);
|
||||
// Encrypt the payload - generates BlobCipherEncryptHeader to assist decryption later
|
||||
BlobCipherEncryptHeader header;
|
||||
try {
|
||||
Reference<EncryptBuf> encrypted = doEncryption(cipherKey, buff.get(), dataLen, &header);
|
||||
|
||||
// decrypt the payload
|
||||
doDecryption(&key, encrypted, dataLen, buff.get(), validationBuff.get());
|
||||
// Decrypt the payload - parses the BlobCipherEncryptHeader, fetch corresponding cipherKey and
|
||||
// decrypt
|
||||
doDecryption(encrypted, dataLen, header, buff.get(), cipherKey);
|
||||
} catch (Error& e) {
|
||||
TraceEvent("Failed")
|
||||
.detail("DomainId", encryptDomainId)
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId());
|
||||
throw;
|
||||
}
|
||||
|
||||
metrics->updateBytes(dataLen);
|
||||
}
|
||||
|
||||
// Cleanup cipherKeys
|
||||
resetCipherEssentials();
|
||||
return Void();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ void forceLinkMemcpyTests();
|
|||
void forceLinkMemcpyPerfTests();
|
||||
#if (!defined(TLS_DISABLED) && !defined(_WIN32))
|
||||
void forceLinkStreamCipherTests();
|
||||
void forceLinkBLockCiherTests();
|
||||
#endif
|
||||
void forceLinkParallelStreamTests();
|
||||
void forceLinkSimExternalConnectionTests();
|
||||
|
@ -76,6 +77,7 @@ struct UnitTestWorkload : TestWorkload {
|
|||
forceLinkMemcpyPerfTests();
|
||||
#if (!defined(TLS_DISABLED) && !defined(_WIN32))
|
||||
forceLinkStreamCipherTests();
|
||||
void forceLinkBlobCipherTests();
|
||||
#endif
|
||||
forceLinkParallelStreamTests();
|
||||
forceLinkSimExternalConnectionTests();
|
||||
|
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
* BlobCipher.cpp
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "flow/BlobCipher.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/ITrace.h"
|
||||
#include "flow/network.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#if ENCRYPTION_ENABLED
|
||||
|
||||
// BlobCipherEncryptHeader
|
||||
BlobCipherEncryptHeader::BlobCipherEncryptHeader() {
|
||||
flags.encryptMode = BLOB_CIPHER_ENCRYPT_MODE_NONE;
|
||||
}
|
||||
|
||||
// BlobCipherKey class methods
|
||||
|
||||
BlobCipherKey::BlobCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen) {
|
||||
BlobCipherRandomSalt salt;
|
||||
if (g_network->isSimulated()) {
|
||||
salt = deterministicRandom()->randomUInt64();
|
||||
} else {
|
||||
salt = nondeterministicRandom()->randomUInt64();
|
||||
}
|
||||
initKey(domainId, baseCiph, baseCiphLen, baseCiphId, salt);
|
||||
/*TraceEvent("BlobCipherKey")
|
||||
.detail("DomainId", domainId)
|
||||
.detail("BaseCipherId", baseCipherId)
|
||||
.detail("BaseCipherLen", baseCipherLen)
|
||||
.detail("RandomSalt", randomSalt)
|
||||
.detail("CreationTime", creationTime);*/
|
||||
}
|
||||
|
||||
void BlobCipherKey::initKey(const BlobCipherDomainId& domainId,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
const BlobCipherRandomSalt& 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);
|
||||
memcpy(baseCipher.get(), baseCiph, std::min<int>(baseCiphLen, AES_256_KEY_LENGTH));
|
||||
baseCipherLen = baseCiphLen;
|
||||
baseCipherId = baseCiphId;
|
||||
// Set the encryption domain for the base encryption key
|
||||
encryptDomainId = domainId;
|
||||
randomSalt = salt;
|
||||
// derive the encryption key
|
||||
cipher = std::make_unique<uint8_t[]>(AES_256_KEY_LENGTH);
|
||||
memset(cipher.get(), 0, AES_256_KEY_LENGTH);
|
||||
applyHmacSha256Derivation();
|
||||
// update the key creation time
|
||||
creationTime = now();
|
||||
}
|
||||
|
||||
void BlobCipherKey::applyHmacSha256Derivation() {
|
||||
Arena arena;
|
||||
uint8_t buf[baseCipherLen + sizeof(BlobCipherRandomSalt)];
|
||||
memcpy(&buf[0], baseCipher.get(), baseCipherLen);
|
||||
memcpy(&buf[0] + baseCipherLen, &randomSalt, sizeof(BlobCipherRandomSalt));
|
||||
HmacSha256DigestGen hmacGen(baseCipher.get(), baseCipherLen);
|
||||
StringRef digest = hmacGen.digest(&buf[0], baseCipherLen + sizeof(BlobCipherRandomSalt), 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());
|
||||
}
|
||||
}
|
||||
|
||||
void BlobCipherKey::reset() {
|
||||
memset(baseCipher.get(), 0, baseCipherLen);
|
||||
memset(cipher.get(), 0, AES_256_KEY_LENGTH);
|
||||
}
|
||||
|
||||
// BlobKeyIdCache class methods
|
||||
|
||||
BlobCipherKeyIdCache::BlobCipherKeyIdCache()
|
||||
: domainId(INVALID_DOMAIN_ID), latestBaseCipherKeyId(INVALID_CIPHER_KEY_ID) {}
|
||||
|
||||
BlobCipherKeyIdCache::BlobCipherKeyIdCache(BlobCipherDomainId dId)
|
||||
: domainId(dId), latestBaseCipherKeyId(INVALID_CIPHER_KEY_ID) {
|
||||
TraceEvent("Init_BlobCipherKeyIdCache").detail("DomainId", domainId);
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyIdCache::getLatestCipherKey() {
|
||||
return getCipherByBaseCipherId(latestBaseCipherKeyId);
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyIdCache::getCipherByBaseCipherId(BlobCipherBaseKeyId baseCipherKeyId) {
|
||||
BlobCipherKeyIdCacheMapCItr itr = keyIdCache.find(baseCipherKeyId);
|
||||
if (itr == keyIdCache.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
}
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
void BlobCipherKeyIdCache::insertBaseCipherKey(BlobCipherBaseKeyId baseCipherId,
|
||||
const uint8_t* baseCipher,
|
||||
int baseCipherLen) {
|
||||
ASSERT(baseCipherId > INVALID_CIPHER_KEY_ID);
|
||||
|
||||
// BaseCipherKeys are immutable, ensure that cached value doesn't get updated.
|
||||
BlobCipherKeyIdCacheMapCItr itr = keyIdCache.find(baseCipherId);
|
||||
if (itr != keyIdCache.end()) {
|
||||
if (memcmp(itr->second->rawBaseCipher(), baseCipher, baseCipherLen) == 0) {
|
||||
TraceEvent("InsertBaseCipherKey_AlreadyPresent")
|
||||
.detail("BaseCipherKeyId", baseCipherId)
|
||||
.detail("DomainId", domainId);
|
||||
// Key is already present; nothing more to do.
|
||||
return;
|
||||
} else {
|
||||
TraceEvent("InsertBaseCipherKey_UpdateCipher")
|
||||
.detail("BaseCipherKeyId", baseCipherId)
|
||||
.detail("DomainId", domainId);
|
||||
throw encrypt_update_cipher();
|
||||
}
|
||||
}
|
||||
|
||||
keyIdCache.emplace(baseCipherId, makeReference<BlobCipherKey>(domainId, baseCipherId, baseCipher, baseCipherLen));
|
||||
// Update the latest BaseCipherKeyId for the given encryption domain
|
||||
latestBaseCipherKeyId = baseCipherId;
|
||||
}
|
||||
|
||||
void BlobCipherKeyIdCache::cleanup() {
|
||||
for (auto& keyItr : keyIdCache) {
|
||||
keyItr.second->reset();
|
||||
}
|
||||
|
||||
keyIdCache.clear();
|
||||
}
|
||||
|
||||
std::vector<Reference<BlobCipherKey>> BlobCipherKeyIdCache::getAllCipherKeys() {
|
||||
std::vector<Reference<BlobCipherKey>> cipherKeys;
|
||||
for (auto& keyItr : keyIdCache) {
|
||||
cipherKeys.push_back(keyItr.second);
|
||||
}
|
||||
return cipherKeys;
|
||||
}
|
||||
|
||||
// BlobCipherKeyCache class methods
|
||||
|
||||
void BlobCipherKeyCache::insertCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCipherId,
|
||||
const uint8_t* baseCipher,
|
||||
int baseCipherLen) {
|
||||
if (domainId == INVALID_DOMAIN_ID || baseCipherId == INVALID_CIPHER_KEY_ID) {
|
||||
throw encrypt_invalid_id();
|
||||
}
|
||||
|
||||
try {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
// Add mapping to track new encryption domain
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = makeReference<BlobCipherKeyIdCache>(domainId);
|
||||
keyIdCache->insertBaseCipherKey(baseCipherId, baseCipher, baseCipherLen);
|
||||
domainCacheMap.emplace(domainId, keyIdCache);
|
||||
} else {
|
||||
// Track new baseCipher keys
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr->second;
|
||||
keyIdCache->insertBaseCipherKey(baseCipherId, baseCipher, baseCipherLen);
|
||||
}
|
||||
|
||||
TraceEvent("InsertCipherKey").detail("DomainId", domainId).detail("BaseCipherKeyId", baseCipherId);
|
||||
} catch (Error& e) {
|
||||
TraceEvent("InsertCipherKey_Failed").detail("BaseCipherKeyId", baseCipherId).detail("DomainId", domainId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getLatestCipherKey(const BlobCipherDomainId& domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
TraceEvent("GetLatestCipherKey_DomainNotFound").detail("DomainId", domainId);
|
||||
throw encrypt_key_not_found();
|
||||
}
|
||||
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr->second;
|
||||
Reference<BlobCipherKey> cipherKey = keyIdCache->getLatestCipherKey();
|
||||
if ((now() - cipherKey->getCreationTime()) > BlobCipherKeyCache::CIPHER_KEY_CACHE_TTL_SEC) {
|
||||
TraceEvent("GetLatestCipherKey_ExpiredTTL")
|
||||
.detail("DomainId", domainId)
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId());
|
||||
throw encrypt_key_ttl_expired();
|
||||
}
|
||||
|
||||
return cipherKey;
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> BlobCipherKeyCache::getCipherKey(const BlobCipherDomainId& domainId,
|
||||
const BlobCipherBaseKeyId& baseCipherId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
}
|
||||
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr->second;
|
||||
return keyIdCache->getCipherByBaseCipherId(baseCipherId);
|
||||
}
|
||||
|
||||
void BlobCipherKeyCache::resetEncyrptDomainId(const BlobCipherDomainId domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
throw encrypt_key_not_found();
|
||||
}
|
||||
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr->second;
|
||||
keyIdCache->cleanup();
|
||||
TraceEvent("ResetEncryptDomainId").detail("DomainId", domainId);
|
||||
}
|
||||
|
||||
void BlobCipherKeyCache::cleanup() noexcept {
|
||||
BlobCipherKeyCache& instance = BlobCipherKeyCache::getInstance();
|
||||
for (auto& domainItr : instance.domainCacheMap) {
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr.second;
|
||||
keyIdCache->cleanup();
|
||||
TraceEvent("BlobCipherKeyCache_Cleanup").detail("DomainId", domainItr.first);
|
||||
}
|
||||
|
||||
instance.domainCacheMap.clear();
|
||||
}
|
||||
|
||||
std::vector<Reference<BlobCipherKey>> BlobCipherKeyCache::getAllCiphers(const BlobCipherDomainId& domainId) {
|
||||
auto domainItr = domainCacheMap.find(domainId);
|
||||
if (domainItr == domainCacheMap.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference<BlobCipherKeyIdCache> keyIdCache = domainItr->second;
|
||||
return keyIdCache->getAllCipherKeys();
|
||||
}
|
||||
|
||||
// EncryptBlobCipher class methods
|
||||
|
||||
EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> key,
|
||||
const uint8_t* cipherIV,
|
||||
const int ivLen)
|
||||
: ctx(EVP_CIPHER_CTX_new()), cipherKey(key) {
|
||||
ASSERT(ivLen == AES_256_IV_LENGTH);
|
||||
memcpy(&iv[0], cipherIV, ivLen);
|
||||
|
||||
if (ctx == nullptr) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
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) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeader* header,
|
||||
Arena& arena) {
|
||||
TEST(true); // Encrypting data with BlobCipher
|
||||
|
||||
Reference<EncryptBuf> encryptBuf = makeReference<EncryptBuf>(plaintextLen + AES_BLOCK_SIZE, 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());
|
||||
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());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
if ((bytes + finalBytes) != plaintextLen) {
|
||||
TraceEvent("Encrypt_UnexpectedCipherLen")
|
||||
.detail("PlaintextLen", plaintextLen)
|
||||
.detail("EncryptedBufLen", bytes + finalBytes);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// populate header details for the encrypted blob.
|
||||
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);
|
||||
|
||||
// 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.
|
||||
|
||||
header->ciphertextChecksum = computeEncryptChecksum(ciphertext, bytes + finalBytes, cipherKey->getSalt(), arena);
|
||||
|
||||
encryptBuf->setLogicalSize(plaintextLen);
|
||||
return encryptBuf;
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr::~EncryptBlobCipherAes265Ctr() {
|
||||
if (ctx != nullptr) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// DecryptBlobCipher class methods
|
||||
|
||||
DecryptBlobCipherAes256Ctr::DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> key, const uint8_t* iv)
|
||||
: ctx(EVP_CIPHER_CTX_new()) {
|
||||
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)) {
|
||||
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();
|
||||
}
|
||||
|
||||
// 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")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("CiphertextChecksum", header.ciphertextChecksum)
|
||||
.detail("ComputedCiphertextChecksum", computed);
|
||||
throw encrypt_header_checksum_mismatch();
|
||||
}
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> DecryptBlobCipherAes256Ctr::decrypt(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena& arena) {
|
||||
TEST(true); // Decrypting data with BlobCipher
|
||||
|
||||
verifyEncryptBlobHeader(ciphertext, ciphertextLen, header, arena);
|
||||
|
||||
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);
|
||||
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);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
if ((bytesDecrypted + finalBlobBytes) != ciphertextLen) {
|
||||
TraceEvent("Encrypt_UnexpectedPlaintextLen")
|
||||
.detail("CiphertextLen", ciphertextLen)
|
||||
.detail("DecryptedBufLen", bytesDecrypted + finalBlobBytes);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
decrypted->setLogicalSize(ciphertextLen);
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
DecryptBlobCipherAes256Ctr::~DecryptBlobCipherAes256Ctr() {
|
||||
if (ctx != nullptr) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// HmacSha256DigestGen class methods
|
||||
|
||||
HmacSha256DigestGen::HmacSha256DigestGen(const unsigned char* key, size_t len) : ctx(HMAC_CTX_new()) {
|
||||
if (!HMAC_Init_ex(ctx, key, len, EVP_sha256(), nullptr)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
||||
HmacSha256DigestGen::~HmacSha256DigestGen() {
|
||||
if (ctx != nullptr) {
|
||||
HMAC_CTX_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
StringRef HmacSha256DigestGen::digest(const unsigned char* data, size_t len, Arena& arena) {
|
||||
TEST(true); // Digest generation
|
||||
unsigned int digestLen = HMAC_size(ctx);
|
||||
auto digest = new (arena) unsigned char[digestLen];
|
||||
if (HMAC_Update(ctx, data, len) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
if (HMAC_Final(ctx, digest, &digestLen) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
return StringRef(digest, digestLen);
|
||||
}
|
||||
|
||||
// Only used to link unit tests
|
||||
void forceLinkBlobCipherTests() {}
|
||||
|
||||
// Tests cases includes:
|
||||
// 1. Populate cache by inserting 'baseCipher' details for new encryptionDomainIds
|
||||
// 2. Random lookup for cipherKeys and content validation
|
||||
// 3. Inserting of 'identical' cipherKey (already cached) more than once works as desired.
|
||||
// 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.
|
||||
// 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;
|
||||
int len;
|
||||
BlobCipherBaseKeyId keyId;
|
||||
std::unique_ptr<uint8_t[]> key;
|
||||
|
||||
BaseCipher(const BlobCipherDomainId& dId, const BlobCipherBaseKeyId& 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>;
|
||||
DomainKeyMap domainKeyMap;
|
||||
const BlobCipherDomainId minDomainId = 1;
|
||||
const BlobCipherDomainId maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 10) + 5;
|
||||
const BlobCipherBaseKeyId minBaseCipherKeyId = 100;
|
||||
const BlobCipherBaseKeyId 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);
|
||||
|
||||
// insert BlobCipher keys into BlobCipherKeyCache map and validate
|
||||
TraceEvent("BlobCipherTest_InsertKeys").log();
|
||||
BlobCipherKeyCache& cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
for (auto& domainItr : domainKeyMap) {
|
||||
for (auto& baseKeyItr : domainItr.second) {
|
||||
Reference<BaseCipher> baseCipher = baseKeyItr.second;
|
||||
|
||||
cipherKeyCache.insertCipherKey(
|
||||
baseCipher->domainId, baseCipher->keyId, baseCipher->key.get(), baseCipher->len);
|
||||
}
|
||||
}
|
||||
TraceEvent("BlobCipherTest_InsertKeysDone").log();
|
||||
|
||||
// validate the cipherKey lookups work as desired
|
||||
for (auto& domainItr : domainKeyMap) {
|
||||
for (auto& baseKeyItr : domainItr.second) {
|
||||
Reference<BaseCipher> baseCipher = baseKeyItr.second;
|
||||
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);
|
||||
// ensure that baseCipher matches with the cached information
|
||||
ASSERT(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);
|
||||
}
|
||||
}
|
||||
TraceEvent("BlobCipherTest_LooksupDone").log();
|
||||
|
||||
// Ensure attemtping to insert existing cipherKey (identical) more than once is treated as a NOP
|
||||
try {
|
||||
Reference<BaseCipher> baseCipher = domainKeyMap[minDomainId][minBaseCipherKeyId];
|
||||
cipherKeyCache.insertCipherKey(baseCipher->domainId, baseCipher->keyId, baseCipher->key.get(), baseCipher->len);
|
||||
} catch (Error& e) {
|
||||
throw;
|
||||
}
|
||||
TraceEvent("BlobCipherTest_ReinsertIdempotentKeyDone").log();
|
||||
|
||||
// Ensure attemtping to insert an existing cipherKey (modified) fails with appropriate error
|
||||
try {
|
||||
Reference<BaseCipher> baseCipher = domainKeyMap[minDomainId][minBaseCipherKeyId];
|
||||
uint8_t rawCipher[baseCipher->len];
|
||||
memcpy(rawCipher, baseCipher->key.get(), baseCipher->len);
|
||||
// modify few bytes in the cipherKey
|
||||
for (int i = 2; i < 5; i++) {
|
||||
rawCipher[i]++;
|
||||
}
|
||||
cipherKeyCache.insertCipherKey(baseCipher->domainId, baseCipher->keyId, &rawCipher[0], baseCipher->len);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_update_cipher) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
TraceEvent("BlobCipherTest_ReinsertNonIdempotentKeyDone").log();
|
||||
|
||||
// Validate Encyrption ops
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache.getLatestCipherKey(minDomainId);
|
||||
const int bufLen = deterministicRandom()->randomInt(786, 2127) + 512;
|
||||
uint8_t orgData[bufLen];
|
||||
generateRandomData(&orgData[0], bufLen);
|
||||
|
||||
Arena arena;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
ASSERT(decrypted->getLogicalSize() == bufLen);
|
||||
ASSERT(memcmp(decrypted->begin(), &orgData[0], bufLen) == 0);
|
||||
|
||||
TraceEvent("BlobCipherTest_DecryptDone").log();
|
||||
|
||||
// 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;
|
||||
}
|
||||
header.flags.headerVersion -= 1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
header.flags.encryptMode -= 1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
header.ciphertextChecksum -= 1;
|
||||
}
|
||||
|
||||
// Validate dropping encyrptDomainId cached keys
|
||||
const BlobCipherDomainId candidate = deterministicRandom()->randomInt(minDomainId, maxDomainId);
|
||||
cipherKeyCache.resetEncyrptDomainId(candidate);
|
||||
std::vector<Reference<BlobCipherKey>> cachedKeys = cipherKeyCache.getAllCiphers(candidate);
|
||||
ASSERT(cachedKeys.empty());
|
||||
|
||||
// Validate dropping all cached cipherKeys
|
||||
cipherKeyCache.cleanup();
|
||||
for (int dId = minDomainId; dId < maxDomainId; dId++) {
|
||||
std::vector<Reference<BlobCipherKey>> cachedKeys = cipherKeyCache.getAllCiphers(dId);
|
||||
ASSERT(cachedKeys.empty());
|
||||
}
|
||||
|
||||
TraceEvent("BlobCipherTest_Done").log();
|
||||
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
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* BlobCipher.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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if (!defined(TLS_DISABLED) && !defined(_WIN32))
|
||||
#define ENCRYPTION_ENABLED 1
|
||||
#else
|
||||
#define ENCRYPTION_ENABLED 0
|
||||
#endif
|
||||
|
||||
#if ENCRYPTION_ENABLED
|
||||
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/xxhash.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#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.
|
||||
// For encryption EncryptBuf is allocated using client supplied Arena and provided to AES library to capture
|
||||
// the ciphertext. Similarly, on decryption EncryptBuf is allocated using client supplied Arena and provided
|
||||
// to the AES library to capture decipher text and passed back to the clients. Given the object passed around
|
||||
// is reference-counted, it gets freed once refrenceCount goes to 0.
|
||||
|
||||
class EncryptBuf : public ReferenceCounted<EncryptBuf>, NonCopyable {
|
||||
public:
|
||||
EncryptBuf(int size, Arena& arena) : allocSize(size), logicalSize(size) {
|
||||
if (size > 0) {
|
||||
buffer = new (arena) uint8_t[size];
|
||||
} else {
|
||||
buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int getLogicalSize() { return logicalSize; }
|
||||
void setLogicalSize(int value) {
|
||||
ASSERT(value <= allocSize);
|
||||
logicalSize = value;
|
||||
}
|
||||
uint8_t* begin() { return buffer; }
|
||||
|
||||
private:
|
||||
int allocSize;
|
||||
int logicalSize;
|
||||
uint8_t* buffer;
|
||||
};
|
||||
|
||||
// BlobCipher Encryption header format
|
||||
// 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.
|
||||
|
||||
#pragma pack(push, 1) // exact fit - no padding
|
||||
typedef struct BlobCipherEncryptHeader {
|
||||
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]{};
|
||||
} 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();
|
||||
} BlobCipherEncryptHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
// 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,
|
||||
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; }
|
||||
int getBaseCipherLen() const { return baseCipherLen; }
|
||||
uint8_t* rawCipher() const { return cipher.get(); }
|
||||
uint8_t* rawBaseCipher() const { return baseCipher.get(); }
|
||||
bool isEqual(const Reference<BlobCipherKey> toCompare) {
|
||||
return encryptDomainId == toCompare->getDomainId() && baseCipherId == toCompare->getBaseCipherId() &&
|
||||
randomSalt == toCompare->getSalt() && baseCipherLen == toCompare->getBaseCipherLen() &&
|
||||
memcmp(cipher.get(), toCompare->rawCipher(), AES_256_KEY_LENGTH) == 0 &&
|
||||
memcmp(baseCipher.get(), toCompare->rawBaseCipher(), baseCipherLen) == 0;
|
||||
}
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// Encryption domain boundary identifier
|
||||
BlobCipherDomainId encryptDomainId;
|
||||
// Base encryption cipher key properties
|
||||
std::unique_ptr<uint8_t[]> baseCipher;
|
||||
int baseCipherLen;
|
||||
BlobCipherBaseKeyId baseCipherId;
|
||||
// Random salt used for encryption cipher key derivation
|
||||
BlobCipherRandomSalt 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,
|
||||
const uint8_t* baseCiph,
|
||||
int baseCiphLen,
|
||||
const BlobCipherBaseKeyId& baseCiphId,
|
||||
const BlobCipherRandomSalt& salt);
|
||||
void applyHmacSha256Derivation();
|
||||
};
|
||||
|
||||
// This interface allows FDB processes participating in encryption to store and
|
||||
// index recently used encyption cipher keys. FDB encryption has two dimensions:
|
||||
// 1. Mapping on cipher encryption keys per "encryption domains"
|
||||
// 2. Per encryption domain, the cipher keys are index using "baseCipherKeyId".
|
||||
//
|
||||
// The design supports NIST recommendation of limiting lifetime of an encryption
|
||||
// key. For details refer to:
|
||||
// https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-3/archive/2012-07-10
|
||||
//
|
||||
// Below gives a pictoral representation of in-memory datastructure implemented
|
||||
// to index encryption keys:
|
||||
// { encryptionDomain -> { baseCipherId -> cipherKey } }
|
||||
//
|
||||
// Supported cache lookups schemes:
|
||||
// 1. Lookup cipher based on { encryptionDomainId, baseCipherKeyId } tuple.
|
||||
// 2. Lookup latest cipher key for a given encryptionDomainId.
|
||||
//
|
||||
// Client is responsible to handle cache-miss usecase, the corrective operation
|
||||
// might vary based on the calling process, for instance: EncryptKeyServer
|
||||
// cache-miss shall invoke RPC to external Encryption Key Manager to fetch the
|
||||
// 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;
|
||||
|
||||
struct BlobCipherKeyIdCache : ReferenceCounted<BlobCipherKeyIdCache> {
|
||||
public:
|
||||
BlobCipherKeyIdCache();
|
||||
explicit BlobCipherKeyIdCache(BlobCipherDomainId 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);
|
||||
// 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);
|
||||
// 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;
|
||||
BlobCipherKeyIdCacheMap keyIdCache;
|
||||
BlobCipherBaseKeyId latestBaseCipherKeyId;
|
||||
};
|
||||
|
||||
using BlobCipherDomainCacheMap = std::unordered_map<BlobCipherDomainId, Reference<BlobCipherKeyIdCache>>;
|
||||
|
||||
class BlobCipherKeyCache : NonCopyable {
|
||||
public:
|
||||
// Enable clients to insert base encryption cipher details to the BlobCipherKeyCache.
|
||||
// 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,
|
||||
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);
|
||||
// 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);
|
||||
// API returns point in time list of all 'cached' cipherKeys for a given encryption domainId.
|
||||
std::vector<Reference<BlobCipherKey>> getAllCiphers(const BlobCipherDomainId& 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);
|
||||
|
||||
static BlobCipherKeyCache& getInstance() {
|
||||
static BlobCipherKeyCache instance;
|
||||
return instance;
|
||||
}
|
||||
// Ensures cached encryption key(s) (plaintext) never gets persisted as part
|
||||
// of FDB process/core dump.
|
||||
static void cleanup() noexcept;
|
||||
|
||||
private:
|
||||
BlobCipherDomainCacheMap domainCacheMap;
|
||||
static constexpr uint64_t CIPHER_KEY_CACHE_TTL_SEC = 10 * 60L;
|
||||
|
||||
BlobCipherKeyCache() {}
|
||||
};
|
||||
|
||||
// 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.
|
||||
|
||||
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<EncryptBuf> encrypt(const uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeader* header,
|
||||
Arena&);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
Reference<BlobCipherKey> cipherKey;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
};
|
||||
|
||||
// This interface enable data block decryption. An invocation to decrypt() would generate
|
||||
// 'plaintext' for a given 'ciphertext' input, the caller needs to supply BlobCipherEncryptHeader.
|
||||
|
||||
class DecryptBlobCipherAes256Ctr final : NonCopyable, public ReferenceCounted<DecryptBlobCipherAes256Ctr> {
|
||||
public:
|
||||
DecryptBlobCipherAes256Ctr(Reference<BlobCipherKey> key, const uint8_t* iv);
|
||||
~DecryptBlobCipherAes256Ctr();
|
||||
Reference<EncryptBuf> decrypt(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena&);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
|
||||
void verifyEncryptBlobHeader(const uint8_t* cipherText,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
Arena& arena);
|
||||
};
|
||||
|
||||
class HmacSha256DigestGen final : NonCopyable {
|
||||
public:
|
||||
HmacSha256DigestGen(const unsigned char* key, size_t len);
|
||||
~HmacSha256DigestGen();
|
||||
HMAC_CTX* getCtx() const { return ctx; }
|
||||
StringRef digest(unsigned char const* data, size_t len, Arena&);
|
||||
|
||||
private:
|
||||
HMAC_CTX* ctx;
|
||||
};
|
||||
|
||||
BlobCipherChecksum computeEncryptChecksum(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
const BlobCipherRandomSalt& salt,
|
||||
Arena& arena);
|
||||
|
||||
#endif // ENCRYPTION_ENABLED
|
|
@ -8,6 +8,8 @@ set(FLOW_SRCS
|
|||
ArgParseUtil.h
|
||||
AsioReactor.h
|
||||
BooleanParam.h
|
||||
BlobCipher.h
|
||||
BlobCipher.cpp
|
||||
CompressedInt.actor.cpp
|
||||
CompressedInt.h
|
||||
Deque.cpp
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
#if (!defined(TLS_DISABLED) && !defined(_WIN32))
|
||||
#include "flow/StreamCipher.h"
|
||||
#include "flow/BlobCipher.h"
|
||||
#endif
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/Error.h"
|
||||
|
@ -3501,6 +3502,7 @@ void crashHandler(int sig) {
|
|||
#if (!defined(TLS_DISABLED) && !defined(_WIN32))
|
||||
StreamCipherKey::cleanup();
|
||||
StreamCipher::cleanup();
|
||||
BlobCipherKeyCache::cleanup();
|
||||
#endif
|
||||
|
||||
fflush(stdout);
|
||||
|
|
|
@ -284,6 +284,15 @@ 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")
|
||||
|
||||
// 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
|
||||
ERROR( internal_error, 4100, "An internal error occurred" )
|
||||
|
|
Loading…
Reference in New Issue