Add inplace encryption/decryption API
This commit is contained in:
parent
117b6c0a6e
commit
0efd403e59
|
@ -950,6 +950,50 @@ void EncryptBlobCipherAes265Ctr::updateEncryptHeader(const uint8_t* ciphertext,
|
|||
setCipherAlgoHeaderV1(ciphertext, ciphertextLen, flags, headerRef);
|
||||
}
|
||||
|
||||
void EncryptBlobCipherAes265Ctr::updateEncryptHeader(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
BlobCipherEncryptHeader* header) {
|
||||
// Populate encryption header flags details
|
||||
header->flags.size = sizeof(BlobCipherEncryptHeader);
|
||||
header->flags.headerVersion = EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION;
|
||||
header->flags.encryptMode = ENCRYPT_CIPHER_MODE_AES_256_CTR;
|
||||
header->flags.authTokenMode = authTokenMode;
|
||||
header->flags.authTokenAlgo = authTokenAlgo;
|
||||
|
||||
// Ensure encryption header authToken details sanity
|
||||
ASSERT(isEncryptHeaderAuthTokenDetailsValid(authTokenMode, authTokenAlgo));
|
||||
|
||||
// Populate cipherText encryption-key details
|
||||
header->cipherTextDetails = textCipherKey->details();
|
||||
// Populate header encryption-key details
|
||||
if (authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
header->cipherHeaderDetails = headerCipherKeyOpt.get()->details();
|
||||
} else {
|
||||
header->cipherHeaderDetails = BlobCipherDetails();
|
||||
ASSERT_EQ(INVALID_ENCRYPT_DOMAIN_ID, header->cipherHeaderDetails.encryptDomainId);
|
||||
ASSERT_EQ(INVALID_ENCRYPT_CIPHER_KEY_ID, header->cipherHeaderDetails.baseCipherId);
|
||||
ASSERT_EQ(INVALID_ENCRYPT_RANDOM_SALT, header->cipherHeaderDetails.salt);
|
||||
}
|
||||
|
||||
memcpy(&header->iv[0], &iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
if (authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
// No header 'authToken' generation needed.
|
||||
} else {
|
||||
|
||||
// Populate header authToken details
|
||||
ASSERT_EQ(header->flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
|
||||
computeAuthToken({ { ciphertext, ciphertextLen },
|
||||
{ reinterpret_cast<const uint8_t*>(header), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKeyOpt.get()->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&header->singleAuthToken.authToken[0],
|
||||
(EncryptAuthTokenAlgo)header->flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
StringRef EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeaderRef* headerRef,
|
||||
|
@ -1003,6 +1047,55 @@ StringRef EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plaintext,
|
|||
return encryptBuf.substr(0, plaintextLen);
|
||||
}
|
||||
|
||||
void EncryptBlobCipherAes265Ctr::encryptInplace(uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeaderRef* headerRef) {
|
||||
double startTime = 0.0;
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
startTime = timer_monotonic();
|
||||
}
|
||||
|
||||
int bytes{ 0 };
|
||||
if (EVP_EncryptUpdate(ctx, plaintext, &bytes, plaintext, plaintextLen) != 1) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUpdateFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// Padding should be 0 for AES CTR mode, so encryptUpdate() should encrypt all the data
|
||||
if (bytes != plaintextLen) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUnexpectedCipherLen")
|
||||
.detail("PlaintextLen", plaintextLen)
|
||||
.detail("EncryptedBufLen", bytes);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// we still call EncryptFinal() to be consistent with encrypt() API. It may not be needed.
|
||||
int finalBytes{ 0 };
|
||||
if (EVP_EncryptFinal_ex(ctx, plaintext + bytes, &finalBytes) != 1) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptFinalFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
ASSERT(finalBytes == 0);
|
||||
|
||||
// Ensure encryption header authToken details sanity
|
||||
ASSERT(isEncryptHeaderAuthTokenDetailsValid(authTokenMode, authTokenAlgo));
|
||||
updateEncryptHeader(plaintext, plaintextLen, headerRef);
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
BlobCipherMetrics::counters(usageType).encryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"encryptInplace: ConfigurableEncryption: Encryption with Auth token generation disabled");
|
||||
CODE_PROBE(authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"encryptInplace: ConfigurableEncryption: Encryption with HMAC_SHA Auth token generation");
|
||||
CODE_PROBE(authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"encryptInplace: ConfigurableEncryption: Encryption with AES_CMAC Auth token generation");
|
||||
}
|
||||
|
||||
Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeader* header,
|
||||
|
@ -1044,47 +1137,7 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// Populate encryption header flags details
|
||||
header->flags.size = sizeof(BlobCipherEncryptHeader);
|
||||
header->flags.headerVersion = EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION;
|
||||
header->flags.encryptMode = ENCRYPT_CIPHER_MODE_AES_256_CTR;
|
||||
header->flags.authTokenMode = authTokenMode;
|
||||
header->flags.authTokenAlgo = authTokenAlgo;
|
||||
|
||||
// Ensure encryption header authToken details sanity
|
||||
ASSERT(isEncryptHeaderAuthTokenDetailsValid(authTokenMode, authTokenAlgo));
|
||||
|
||||
// Populate cipherText encryption-key details
|
||||
header->cipherTextDetails = textCipherKey->details();
|
||||
// Populate header encryption-key details
|
||||
if (authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
header->cipherHeaderDetails = headerCipherKeyOpt.get()->details();
|
||||
} else {
|
||||
header->cipherHeaderDetails = BlobCipherDetails();
|
||||
ASSERT_EQ(INVALID_ENCRYPT_DOMAIN_ID, header->cipherHeaderDetails.encryptDomainId);
|
||||
ASSERT_EQ(INVALID_ENCRYPT_CIPHER_KEY_ID, header->cipherHeaderDetails.baseCipherId);
|
||||
ASSERT_EQ(INVALID_ENCRYPT_RANDOM_SALT, header->cipherHeaderDetails.salt);
|
||||
}
|
||||
|
||||
memcpy(&header->iv[0], &iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
if (authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
// No header 'authToken' generation needed.
|
||||
} else {
|
||||
|
||||
// Populate header authToken details
|
||||
ASSERT_EQ(header->flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
ASSERT_GE(allocSize, (bytes + finalBytes));
|
||||
ASSERT_GE(encryptBuf->getLogicalSize(), (bytes + finalBytes));
|
||||
|
||||
computeAuthToken({ { ciphertext, bytes + finalBytes },
|
||||
{ reinterpret_cast<const uint8_t*>(header), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKeyOpt.get()->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&header->singleAuthToken.authToken[0],
|
||||
(EncryptAuthTokenAlgo)header->flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
}
|
||||
updateEncryptHeader(ciphertext, plaintextLen, header);
|
||||
|
||||
encryptBuf->setLogicalSize(plaintextLen);
|
||||
|
||||
|
@ -1103,6 +1156,57 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
return encryptBuf;
|
||||
}
|
||||
|
||||
void EncryptBlobCipherAes265Ctr::encryptInplace(uint8_t* plaintext,
|
||||
const int plaintextLen,
|
||||
BlobCipherEncryptHeader* header) {
|
||||
double startTime = 0.0;
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
startTime = timer_monotonic();
|
||||
}
|
||||
|
||||
memset(reinterpret_cast<uint8_t*>(header), 0, sizeof(BlobCipherEncryptHeader));
|
||||
|
||||
int bytes{ 0 };
|
||||
if (EVP_EncryptUpdate(ctx, plaintext, &bytes, plaintext, plaintextLen) != 1) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUpdateFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// Padding should be 0 for AES CTR mode, so encryptUpdate() should encrypt all the data
|
||||
if (bytes != plaintextLen) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUnexpectedCipherLen")
|
||||
.detail("PlaintextLen", plaintextLen)
|
||||
.detail("EncryptedBufLen", bytes);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// we still call EncryptFinal() to be consistent with encrypt() API. It may not be needed.
|
||||
int finalBytes{ 0 };
|
||||
if (EVP_EncryptFinal_ex(ctx, plaintext + bytes, &finalBytes) != 1) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptFinalFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
ASSERT(finalBytes == 0);
|
||||
|
||||
updateEncryptHeader(plaintext, plaintextLen, header);
|
||||
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
BlobCipherMetrics::counters(usageType).encryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(true, "encryptInplace: BlobCipher data encryption");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"encryptInplace: Encryption authentication disabled");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"encryptInplace: HMAC_SHA Auth token generation");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"encryptInplace: AES_CMAC Auth token generation");
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr::~EncryptBlobCipherAes265Ctr() {
|
||||
if (ctx != nullptr) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
@ -1440,6 +1544,117 @@ Reference<EncryptBuf> DecryptBlobCipherAes256Ctr::decrypt(const uint8_t* ciphert
|
|||
return decrypted;
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::decryptInplace(uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header) {
|
||||
double startTime = 0.0;
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
startTime = timer_monotonic();
|
||||
}
|
||||
|
||||
verifyEncryptHeaderMetadata(header);
|
||||
|
||||
if (header.flags.authTokenMode != EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
|
||||
(!headerCipherKeyOpt.present() || !headerCipherKeyOpt.get().isValid())) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptInvalidHeaderCipherKey")
|
||||
.detail("AuthTokenMode", header.flags.authTokenMode);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
if (header.flags.authTokenMode != EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
verifyAuthTokens(ciphertext, ciphertextLen, header);
|
||||
ASSERT(authTokensValidationDone);
|
||||
}
|
||||
|
||||
int bytesDecrypted{ 0 };
|
||||
if (!EVP_DecryptUpdate(ctx, ciphertext, &bytesDecrypted, ciphertext, ciphertextLen)) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptUpdateFailed")
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("EncryptDomainId", header.cipherTextDetails.encryptDomainId);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// Padding should be 0 for AES CTR mode, so DecryptUpdate() should decrypt all the data
|
||||
if (bytesDecrypted != ciphertextLen) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUnexpectedPlaintextLen")
|
||||
.detail("CiphertextLen", ciphertextLen)
|
||||
.detail("DecryptedBufLen", bytesDecrypted);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// we still call DecryptFinal() to be consistent with decrypt() API. It may not be needed.
|
||||
int finalBlobBytes{ 0 };
|
||||
if (EVP_DecryptFinal_ex(ctx, ciphertext + bytesDecrypted, &finalBlobBytes) <= 0) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptFinalFailed")
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("EncryptDomainId", header.cipherTextDetails.encryptDomainId);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
ASSERT(finalBlobBytes == 0);
|
||||
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
BlobCipherMetrics::counters(usageType).decryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(true, "decryptInplace: BlobCipher data decryption");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"decryptInplace: Decryption authentication disabled");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"decryptInplace: Decryption HMAC_SHA Auth token verification");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"decryptInplace: Decryption AES_CMAC Auth token verification");
|
||||
}
|
||||
|
||||
void DecryptBlobCipherAes256Ctr::decryptInplace(uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeaderRef& headerRef) {
|
||||
double startTime = 0.0;
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
startTime = timer_monotonic();
|
||||
}
|
||||
|
||||
EncryptAuthTokenMode authTokenMode;
|
||||
EncryptAuthTokenAlgo authTokenAlgo;
|
||||
validateEncryptHeader(ciphertext, ciphertextLen, headerRef, &authTokenMode, &authTokenAlgo);
|
||||
|
||||
int bytesDecrypted{ 0 };
|
||||
if (!EVP_DecryptUpdate(ctx, ciphertext, &bytesDecrypted, ciphertext, ciphertextLen)) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptUpdateFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// Padding should be 0 for AES CTR mode, so DecryptUpdate() should decrypt all the data
|
||||
if (bytesDecrypted != ciphertextLen) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUnexpectedPlaintextLen")
|
||||
.detail("CiphertextLen", ciphertextLen)
|
||||
.detail("DecryptedBufLen", bytesDecrypted);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
// we still call DecryptFinal() to be consistent with decrypt() API. It may not be needed.
|
||||
int finalBlobBytes{ 0 };
|
||||
if (EVP_DecryptFinal_ex(ctx, ciphertext + bytesDecrypted, &finalBlobBytes) <= 0) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptFinalFailed")
|
||||
.detail("BaseCipherId", textCipherKey->getBaseCipherId())
|
||||
.detail("EncryptDomainId", textCipherKey->getDomainId());
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
ASSERT(finalBlobBytes == 0);
|
||||
|
||||
if (CLIENT_KNOBS->ENABLE_ENCRYPTION_CPU_TIME_LOGGING) {
|
||||
BlobCipherMetrics::counters(usageType).decryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"decryptInplace: ConfigurableEncryption: Decryption with Auth token generation disabled");
|
||||
CODE_PROBE(authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"decryptInplace: ConfigurableEncryption: Decryption with HMAC_SHA Auth token generation");
|
||||
CODE_PROBE(authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"decryptInplace: ConfigurableEncryption: Decryption with AES_CMAC Auth token generation");
|
||||
}
|
||||
|
||||
DecryptBlobCipherAes256Ctr::~DecryptBlobCipherAes256Ctr() {
|
||||
if (ctx != nullptr) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
@ -2382,6 +2597,108 @@ void testKeyCacheCleanup(const int minDomainId, const int maxDomainId) {
|
|||
TraceEvent("BlobCipherTestKeyCacheCleanupDone");
|
||||
}
|
||||
|
||||
void testEncryptInplaceNoAuthMode(const int minDomainId) {
|
||||
TraceEvent("EncryptInplaceStart");
|
||||
|
||||
auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection();
|
||||
g_knobs.setKnob("encrypt_inplace_enabled", KnobValueRef::create(bool{ true }));
|
||||
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
// Validate Encryption ops
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache->getLatestCipherKey(minDomainId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache->getLatestCipherKey(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
const int bufLen = deterministicRandom()->randomInt(786, 2127) + 512;
|
||||
alignas(AES_BLOCK_SIZE) uint8_t orgData[bufLen];
|
||||
deterministicRandom()->randomBytes(&orgData[0], bufLen);
|
||||
uint8_t dataClone[bufLen];
|
||||
memcpy(dataClone, orgData, bufLen);
|
||||
|
||||
Arena arena;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
deterministicRandom()->randomBytes(&iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
BlobCipherMetrics::TEST);
|
||||
|
||||
BlobCipherEncryptHeaderRef headerRef;
|
||||
encryptor.encryptInplace(&orgData[0], bufLen, &headerRef);
|
||||
|
||||
// validate header version details
|
||||
AesCtrNoAuth noAuth = std::get<AesCtrNoAuth>(headerRef.algoHeader);
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache->getCipherKey(noAuth.v1.cipherTextDetails.encryptDomainId,
|
||||
noAuth.v1.cipherTextDetails.baseCipherId,
|
||||
noAuth.v1.cipherTextDetails.salt);
|
||||
ASSERT(tCipherKeyKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &noAuth.v1.iv[0], BlobCipherMetrics::TEST);
|
||||
|
||||
decryptor.decryptInplace(&orgData[0], bufLen, headerRef);
|
||||
ASSERT_EQ(memcmp(dataClone, &orgData[0], bufLen), 0);
|
||||
|
||||
// try to encrypt/decrypt un-aligned data (start from the offset 1), which should still work but fall back to
|
||||
// non-inplace encrypt/decrypt.
|
||||
encryptor.encryptInplace(&orgData[1], bufLen - 1, &headerRef);
|
||||
|
||||
decryptor.decryptInplace(&orgData[1], bufLen - 1, headerRef);
|
||||
ASSERT_EQ(memcmp(&dataClone[1], &orgData[1], bufLen - 1), 0);
|
||||
|
||||
TraceEvent("EncryptInplaceDone");
|
||||
}
|
||||
|
||||
template <class Params>
|
||||
void testEncryptInplaceSingleAuthMode(const int minDomainId) {
|
||||
constexpr bool isHmac = std::is_same_v<Params, AesCtrWithHmacParams>;
|
||||
const std::string authAlgoStr = isHmac ? "HMAC-SHA" : "AES-CMAC";
|
||||
const EncryptAuthTokenAlgo authAlgo = isHmac ? EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA
|
||||
: EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC;
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptInplaceSingleAuthStart").detail("Mode", authAlgoStr);
|
||||
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
// Validate Encryption ops
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache->getLatestCipherKey(minDomainId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache->getLatestCipherKey(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
const int bufLen = deterministicRandom()->randomInt(786, 2127) + 512;
|
||||
Arena arena;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
deterministicRandom()->randomBytes(&iv[0], AES_256_IV_LENGTH);
|
||||
uint8_t orgData[bufLen + 100];
|
||||
memset(orgData + bufLen, 0, 100);
|
||||
deterministicRandom()->randomBytes(&orgData[0], bufLen);
|
||||
uint8_t dataClone[bufLen];
|
||||
memcpy(dataClone, orgData, bufLen);
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
authAlgo,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
encryptor.encryptInplace(&orgData[0], bufLen, &header);
|
||||
uint8_t empty_buff[100];
|
||||
memset(empty_buff, 0, 100);
|
||||
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache->getCipherKey(
|
||||
header.cipherTextDetails.encryptDomainId, header.cipherTextDetails.baseCipherId, header.cipherTextDetails.salt);
|
||||
Reference<BlobCipherKey> hCipherKey = cipherKeyCache->getCipherKey(header.cipherHeaderDetails.encryptDomainId,
|
||||
header.cipherHeaderDetails.baseCipherId,
|
||||
header.cipherHeaderDetails.salt);
|
||||
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decryptor.decryptInplace(&orgData[0], bufLen, header);
|
||||
ASSERT_EQ(memcmp(dataClone, &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptInplaceSingleAuthEnd").detail("Mode", authAlgoStr);
|
||||
}
|
||||
|
||||
TEST_CASE("/blobCipher") {
|
||||
DomainKeyMap domainKeyMap;
|
||||
auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection();
|
||||
|
@ -2420,6 +2737,11 @@ TEST_CASE("/blobCipher") {
|
|||
testConfigurableEncryptionNoAuthMode(minDomainId);
|
||||
testConfigurableEncryptionSingleAuthMode<AesCtrWithHmacParams>(minDomainId);
|
||||
testConfigurableEncryptionSingleAuthMode<AesCtrWithCmacParams>(minDomainId);
|
||||
|
||||
testEncryptInplaceNoAuthMode(minDomainId);
|
||||
testEncryptInplaceSingleAuthMode<AesCtrWithHmacParams>(minDomainId);
|
||||
testEncryptInplaceSingleAuthMode<AesCtrWithCmacParams>(minDomainId);
|
||||
|
||||
testKeyCacheCleanup(minDomainId, maxDomainId);
|
||||
|
||||
return Void();
|
||||
|
|
|
@ -900,10 +900,15 @@ public:
|
|||
Arena&);
|
||||
StringRef encrypt(const uint8_t*, const int, BlobCipherEncryptHeaderRef*, Arena&);
|
||||
|
||||
void encryptInplace(uint8_t* plaintext, const int plaintextLen, BlobCipherEncryptHeader* header);
|
||||
|
||||
void encryptInplace(uint8_t* plaintext, const int plaintextLen, BlobCipherEncryptHeaderRef* headerRef);
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
void updateEncryptHeader(const uint8_t*, const int, BlobCipherEncryptHeaderRef* headerRef);
|
||||
void updateEncryptHeader(const uint8_t*, const int, BlobCipherEncryptHeader* header);
|
||||
void updateEncryptHeaderFlagsV1(BlobCipherEncryptHeaderRef* headerRef, BlobCipherEncryptHeaderFlagsV1* flags);
|
||||
void setCipherAlgoHeaderV1(const uint8_t*,
|
||||
const int,
|
||||
|
@ -945,6 +950,10 @@ public:
|
|||
const BlobCipherEncryptHeaderRef& headerRef,
|
||||
Arena&);
|
||||
|
||||
void decryptInplace(uint8_t* ciphertext, const int ciphertextLen, const BlobCipherEncryptHeader& header);
|
||||
|
||||
void decryptInplace(uint8_t* ciphertext, const int ciphertextLen, const BlobCipherEncryptHeaderRef& headerRef);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX* ctx;
|
||||
BlobCipherMetrics::UsageType usageType;
|
||||
|
|
|
@ -433,21 +433,35 @@ public:
|
|||
getEncryptAuthTokenMode(ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
BlobCipherMetrics::KV_REDWOOD);
|
||||
Arena arena;
|
||||
StringRef ciphertext;
|
||||
|
||||
if (CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION) {
|
||||
BlobCipherEncryptHeaderRef headerRef;
|
||||
ciphertext = cipher.encrypt(payload, len, &headerRef, arena);
|
||||
if (FLOW_KNOBS->ENCRYPT_INPLACE_ENABLED) {
|
||||
cipher.encryptInplace(payload, len, &headerRef);
|
||||
} else {
|
||||
StringRef ciphertext = cipher.encrypt(payload, len, &headerRef, arena);
|
||||
ASSERT_EQ(len, ciphertext.size());
|
||||
memcpy(payload, ciphertext.begin(), len);
|
||||
}
|
||||
|
||||
Standalone<StringRef> serializedHeader = BlobCipherEncryptHeaderRef::toStringRef(headerRef);
|
||||
ASSERT(serializedHeader.size() <= headerSize);
|
||||
ASSERT(serializedHeader.size() <= BlobCipherEncryptHeader::headerSize);
|
||||
memcpy(h->encryptionHeaderBuf, serializedHeader.begin(), serializedHeader.size());
|
||||
if (serializedHeader.size() < headerSize) {
|
||||
memset(h->encryptionHeaderBuf + serializedHeader.size(), 0, headerSize - serializedHeader.size());
|
||||
if (serializedHeader.size() < BlobCipherEncryptHeader::headerSize) {
|
||||
memset(h->encryptionHeaderBuf + serializedHeader.size(),
|
||||
0,
|
||||
BlobCipherEncryptHeader::headerSize - serializedHeader.size());
|
||||
}
|
||||
} else {
|
||||
ciphertext = cipher.encrypt(payload, len, &h->encryption, arena)->toStringRef();
|
||||
if (FLOW_KNOBS->ENCRYPT_INPLACE_ENABLED) {
|
||||
cipher.encryptInplace(payload, len, &h->encryption);
|
||||
} else {
|
||||
StringRef ciphertext = cipher.encrypt(payload, len, &h->encryption, arena)->toStringRef();
|
||||
ASSERT_EQ(len, ciphertext.size());
|
||||
memcpy(payload, ciphertext.begin(), len);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(len, ciphertext.size());
|
||||
memcpy(payload, ciphertext.begin(), len);
|
||||
|
||||
if constexpr (encodingType == AESEncryption) {
|
||||
h->checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
}
|
||||
|
@ -472,23 +486,33 @@ public:
|
|||
}
|
||||
}
|
||||
Arena arena;
|
||||
StringRef plaintext;
|
||||
if (CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION) {
|
||||
BlobCipherEncryptHeaderRef headerRef = getEncryptionHeaderRef(header);
|
||||
DecryptBlobCipherAes256Ctr cipher(cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
headerRef.getIV(),
|
||||
BlobCipherMetrics::KV_REDWOOD);
|
||||
plaintext = cipher.decrypt(payload, len, headerRef, arena);
|
||||
if (FLOW_KNOBS->ENCRYPT_INPLACE_ENABLED) {
|
||||
cipher.decryptInplace(payload, len, headerRef);
|
||||
} else {
|
||||
StringRef plaintext = cipher.decrypt(payload, len, headerRef, arena);
|
||||
ASSERT_EQ(len, plaintext.size());
|
||||
memcpy(payload, plaintext.begin(), len);
|
||||
}
|
||||
|
||||
} else {
|
||||
DecryptBlobCipherAes256Ctr cipher(cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
h->encryption.iv,
|
||||
BlobCipherMetrics::KV_REDWOOD);
|
||||
plaintext = cipher.decrypt(payload, len, h->encryption, arena)->toStringRef();
|
||||
if (FLOW_KNOBS->ENCRYPT_INPLACE_ENABLED) {
|
||||
cipher.decryptInplace(payload, len, h->encryption);
|
||||
} else {
|
||||
StringRef plaintext = cipher.decrypt(payload, len, h->encryption, arena)->toStringRef();
|
||||
ASSERT_EQ(len, plaintext.size());
|
||||
memcpy(payload, plaintext.begin(), len);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(len, plaintext.size());
|
||||
memcpy(payload, plaintext.begin(), len);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -119,4 +119,4 @@ struct ReadAfterWriteWorkload : KVWorkload {
|
|||
}
|
||||
};
|
||||
|
||||
WorkloadFactory<ReadAfterWriteWorkload> ReadAfterWriteWorkloadFactory;
|
||||
WorkloadFactory<ReadAfterWriteWorkload> decryptionReadAfterWriteWorkloadFactory;
|
||||
|
|
|
@ -316,7 +316,8 @@ void FlowKnobs::initialize(Randomize randomize, IsSimulated isSimulated) {
|
|||
// start exponential backoff at 5s when reaching out to the KMS from EKP
|
||||
init( EKP_KMS_CONNECTION_BACKOFF, 5.0 );
|
||||
// number of times to retry KMS requests from the EKP (roughly attempt to reach out to the KMS for a total of 5 minutes)
|
||||
init( EKP_KMS_CONNECTION_RETRIES, 6 );
|
||||
init( EKP_KMS_CONNECTION_RETRIES, 6 );
|
||||
init( ENCRYPT_INPLACE_ENABLED, false ); if ( randomize && BUGGIFY ) { ENCRYPT_INPLACE_ENABLED = true; }
|
||||
|
||||
// REST Client
|
||||
init( RESTCLIENT_MAX_CONNECTIONPOOL_SIZE, 10 );
|
||||
|
|
|
@ -377,6 +377,7 @@ public:
|
|||
int ENCRYPT_HEADER_AUTH_TOKEN_ALGO;
|
||||
double EKP_KMS_CONNECTION_BACKOFF;
|
||||
int EKP_KMS_CONNECTION_RETRIES;
|
||||
bool ENCRYPT_INPLACE_ENABLED; // Encrypt the page inplace
|
||||
|
||||
// RESTClient
|
||||
int RESTCLIENT_MAX_CONNECTIONPOOL_SIZE;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#include "flow/StreamCipher.h"
|
||||
#include "flowbench/GlobalData.h"
|
||||
|
||||
|
@ -77,3 +78,154 @@ static void bench_decrypt(benchmark::State& state) {
|
|||
|
||||
BENCHMARK(bench_encrypt)->Ranges({ { 1 << 12, 1 << 20 }, { 1, 1 << 12 } });
|
||||
BENCHMARK(bench_decrypt)->Ranges({ { 1 << 12, 1 << 20 }, { 1, 1 << 12 } });
|
||||
|
||||
// blob_chipher* benchmarks are following the encrypt and decrypt unittests from BlobCipher.cpp
|
||||
// Construct a dummy External Key Manager representation and populate with some keys
|
||||
class BaseCipher : public ReferenceCounted<BaseCipher>, NonCopyable {
|
||||
public:
|
||||
EncryptCipherDomainId domainId;
|
||||
int len;
|
||||
EncryptCipherBaseKeyId keyId;
|
||||
std::unique_ptr<uint8_t[]> key;
|
||||
int64_t refreshAt;
|
||||
int64_t expireAt;
|
||||
EncryptCipherRandomSalt generatedSalt;
|
||||
|
||||
BaseCipher(const EncryptCipherDomainId& dId,
|
||||
const EncryptCipherBaseKeyId& kId,
|
||||
const int64_t rAt,
|
||||
const int64_t eAt)
|
||||
: domainId(dId), len(deterministicRandom()->randomInt(AES_256_KEY_LENGTH / 2, AES_256_KEY_LENGTH + 1)),
|
||||
keyId(kId), key(std::make_unique<uint8_t[]>(len)), refreshAt(rAt), expireAt(eAt) {
|
||||
deterministicRandom()->randomBytes(key.get(), len);
|
||||
}
|
||||
};
|
||||
|
||||
using BaseKeyMap = std::unordered_map<EncryptCipherBaseKeyId, Reference<BaseCipher>>;
|
||||
using DomainKeyMap = std::unordered_map<EncryptCipherDomainId, BaseKeyMap>;
|
||||
|
||||
void static SetupEncryptCipher() {
|
||||
DomainKeyMap domainKeyMap;
|
||||
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, std::numeric_limits<int64_t>::max(), std::numeric_limits<int64_t>::max()));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(domainKeyMap.size(), maxDomainId);
|
||||
|
||||
Reference<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,
|
||||
baseCipher->refreshAt,
|
||||
baseCipher->expireAt);
|
||||
Reference<BlobCipherKey> fetchedKey = cipherKeyCache->getLatestCipherKey(baseCipher->domainId);
|
||||
baseCipher->generatedSalt = fetchedKey->getSalt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blob_chipher_encrypt(benchmark::State& state) {
|
||||
const EncryptCipherDomainId minDomainId = 1;
|
||||
const int pageLen = state.range(0);
|
||||
const bool isInplace = state.range(1);
|
||||
|
||||
SetupEncryptCipher();
|
||||
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache->getLatestCipherKey(minDomainId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache->getLatestCipherKey(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
Arena arena;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
deterministicRandom()->randomBytes(&iv[0], AES_256_IV_LENGTH);
|
||||
uint8_t orgData[pageLen];
|
||||
deterministicRandom()->randomBytes(&orgData[0], pageLen);
|
||||
|
||||
for (auto _ : state) {
|
||||
// create a new encryptor for each encrypt operation to simulate AESEncryptionEncoder.encode()
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
BlobCipherMetrics::TEST);
|
||||
|
||||
BlobCipherEncryptHeader header;
|
||||
if (isInplace) {
|
||||
encryptor.encryptInplace(&orgData[0], pageLen, &header);
|
||||
} else {
|
||||
StringRef ciphertext = encryptor.encrypt(&orgData[0], pageLen, &header, arena)->toStringRef();
|
||||
memcpy(orgData, ciphertext.begin(), pageLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blob_chipher_decrypt(benchmark::State& state) {
|
||||
const EncryptCipherDomainId minDomainId = 1;
|
||||
const int pageLen = state.range(0);
|
||||
const bool isInplace = state.range(1);
|
||||
|
||||
SetupEncryptCipher();
|
||||
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
Reference<BlobCipherKey> cipherKey = cipherKeyCache->getLatestCipherKey(minDomainId);
|
||||
Reference<BlobCipherKey> headerCipherKey = cipherKeyCache->getLatestCipherKey(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
Arena arena;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
deterministicRandom()->randomBytes(&iv[0], AES_256_IV_LENGTH);
|
||||
uint8_t orgData[pageLen];
|
||||
deterministicRandom()->randomBytes(&orgData[0], pageLen);
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
BlobCipherMetrics::TEST);
|
||||
|
||||
BlobCipherEncryptHeaderRef headerRef;
|
||||
encryptor.encryptInplace(&orgData[0], pageLen, &headerRef);
|
||||
|
||||
AesCtrNoAuth noAuth = std::get<AesCtrNoAuth>(headerRef.algoHeader);
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache->getCipherKey(noAuth.v1.cipherTextDetails.encryptDomainId,
|
||||
noAuth.v1.cipherTextDetails.baseCipherId,
|
||||
noAuth.v1.cipherTextDetails.salt);
|
||||
|
||||
for (auto _ : state) {
|
||||
// create decryptor for every decrypt operation to simulate AESEncryptionEncoder.decode()
|
||||
DecryptBlobCipherAes256Ctr decryptor(
|
||||
tCipherKeyKey, Reference<BlobCipherKey>(), &noAuth.v1.iv[0], BlobCipherMetrics::TEST);
|
||||
if (isInplace) {
|
||||
decryptor.decryptInplace(&orgData[0], pageLen, headerRef);
|
||||
} else {
|
||||
StringRef decrypted = decryptor.decrypt(&orgData[0], pageLen, headerRef, arena);
|
||||
memcpy(orgData, decrypted.begin(), pageLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blob_chipher_args(benchmark::internal::Benchmark* b) {
|
||||
for (int pageLen : { 8000, 16000 }) {
|
||||
for (bool isInplace : { false, true }) {
|
||||
b->Args({ pageLen, isInplace });
|
||||
}
|
||||
}
|
||||
b->ArgNames({ "pageLen", "isInplace" });
|
||||
}
|
||||
|
||||
BENCHMARK(blob_chipher_encrypt)->Apply(blob_chipher_args);
|
||||
BENCHMARK(blob_chipher_decrypt)->Apply(blob_chipher_args);
|
||||
|
|
Loading…
Reference in New Issue