Enable encryption authentication configurability (#8312)
* Enable encryption authentication configurability Description diff-1: Remove memcpy due to auth-token computation Address review comments Patch proposes major changes: 1. Enable FDB to choose encryption authentication as a configurable parameter. Fix issues choosing ENCRYPT_HEADER_AUTH_TOKEN_NONE mode. 2. Introduce AES_CMAC as supported encryption authentication scheme. Patch allows cluster to govern: if encryption authentication needs to enabled, if yes, then choose from two supported schemes: 1. HMAC_SHA_256 2. AES_256_CMAC Testing devRunCorrectness - 100K BlobCipher unittests EncryptionOps.toml BlobGranuleCorrectness/BlobGranuleCorrectnessClean
This commit is contained in:
parent
63b8d775a3
commit
03f1d13be3
|
@ -50,12 +50,6 @@
|
|||
|
||||
#define BLOB_CIPHER_DEBUG false
|
||||
|
||||
namespace {
|
||||
bool isEncryptHeaderAuthTokenModeValid(const EncryptAuthTokenMode mode) {
|
||||
return mode >= ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && mode < ENCRYPT_HEADER_AUTH_TOKEN_LAST;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// BlobCipherMetrics methods
|
||||
|
||||
BlobCipherMetrics::CounterSet::CounterSet(CounterCollection& cc, std::string name)
|
||||
|
@ -168,8 +162,8 @@ void BlobCipherKey::applyHmacSha256Derivation() {
|
|||
memcpy(&buf[0], baseCipher.get(), baseCipherLen);
|
||||
memcpy(&buf[0] + baseCipherLen, &randomSalt, sizeof(EncryptCipherRandomSalt));
|
||||
HmacSha256DigestGen hmacGen(baseCipher.get(), baseCipherLen);
|
||||
unsigned int digestLen =
|
||||
hmacGen.digest(&buf[0], baseCipherLen + sizeof(EncryptCipherRandomSalt), cipher.get(), AUTH_TOKEN_SIZE);
|
||||
unsigned int digestLen = hmacGen.digest(
|
||||
{ { &buf[0], baseCipherLen + sizeof(EncryptCipherRandomSalt) } }, cipher.get(), AUTH_TOKEN_HMAC_SHA_SIZE);
|
||||
if (digestLen < AES_256_KEY_LENGTH) {
|
||||
memcpy(cipher.get() + digestLen, buf, AES_256_KEY_LENGTH - digestLen);
|
||||
}
|
||||
|
@ -510,7 +504,21 @@ EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey>
|
|||
BlobCipherMetrics::UsageType usageType)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey), authTokenMode(mode),
|
||||
usageType(usageType) {
|
||||
ASSERT(isEncryptHeaderAuthTokenModeValid(mode));
|
||||
ASSERT_EQ(ivLen, AES_256_IV_LENGTH);
|
||||
authTokenAlgo = getAuthTokenAlgoFromMode(authTokenMode);
|
||||
memcpy(&iv[0], cipherIV, ivLen);
|
||||
init();
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* cipherIV,
|
||||
const int ivLen,
|
||||
const EncryptAuthTokenMode mode,
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
BlobCipherMetrics::UsageType usageType)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey), authTokenMode(mode),
|
||||
authTokenAlgo(algo), usageType(usageType) {
|
||||
ASSERT_EQ(ivLen, AES_256_IV_LENGTH);
|
||||
memcpy(&iv[0], cipherIV, ivLen);
|
||||
init();
|
||||
|
@ -522,12 +530,33 @@ EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey>
|
|||
BlobCipherMetrics::UsageType usageType)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey), authTokenMode(mode),
|
||||
usageType(usageType) {
|
||||
ASSERT(isEncryptHeaderAuthTokenModeValid(mode));
|
||||
authTokenAlgo = getAuthTokenAlgoFromMode(authTokenMode);
|
||||
deterministicRandom()->randomBytes(iv, AES_256_IV_LENGTH);
|
||||
init();
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr::EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const EncryptAuthTokenMode mode,
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
BlobCipherMetrics::UsageType usageType)
|
||||
: ctx(EVP_CIPHER_CTX_new()), textCipherKey(tCipherKey), headerCipherKey(hCipherKey), authTokenMode(mode),
|
||||
usageType(usageType) {
|
||||
deterministicRandom()->randomBytes(iv, AES_256_IV_LENGTH);
|
||||
init();
|
||||
}
|
||||
|
||||
void EncryptBlobCipherAes265Ctr::init() {
|
||||
ASSERT(textCipherKey.isValid());
|
||||
ASSERT(headerCipherKey.isValid());
|
||||
|
||||
if (!isEncryptHeaderAuthTokenDetailsValid(authTokenMode, authTokenAlgo)) {
|
||||
TraceEvent(SevWarn, "InvalidAuthTokenDetails")
|
||||
.detail("TokenMode", authTokenMode)
|
||||
.detail("TokenAlgo", authTokenAlgo);
|
||||
throw internal_error();
|
||||
}
|
||||
|
||||
if (ctx == nullptr) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
@ -553,11 +582,10 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
// Alloc buffer computation accounts for 'header authentication' generation scheme. If single-auth-token needs
|
||||
// to be generated, allocate buffer sufficient to append header to the cipherText optimizing memcpy cost.
|
||||
|
||||
const int allocSize = authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
? plaintextLen + AES_BLOCK_SIZE + sizeof(BlobCipherEncryptHeader)
|
||||
: plaintextLen + AES_BLOCK_SIZE;
|
||||
const int allocSize = plaintextLen + AES_BLOCK_SIZE;
|
||||
Reference<EncryptBuf> encryptBuf = makeReference<EncryptBuf>(allocSize, arena);
|
||||
uint8_t* ciphertext = encryptBuf->begin();
|
||||
|
||||
int bytes{ 0 };
|
||||
if (EVP_EncryptUpdate(ctx, ciphertext, &bytes, plaintext, plaintextLen) != 1) {
|
||||
TraceEvent(SevWarn, "BlobCipherEncryptUpdateFailed")
|
||||
|
@ -586,50 +614,55 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
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.baseCipherId = textCipherKey->getBaseCipherId();
|
||||
header->cipherTextDetails.encryptDomainId = textCipherKey->getDomainId();
|
||||
header->cipherTextDetails.salt = textCipherKey->getSalt();
|
||||
// Populate header encryption-key details
|
||||
// TODO: HeaderCipherKey is not necessary if AuthTokenMode == NONE
|
||||
header->cipherHeaderDetails.encryptDomainId = headerCipherKey->getDomainId();
|
||||
header->cipherHeaderDetails.baseCipherId = headerCipherKey->getBaseCipherId();
|
||||
header->cipherHeaderDetails.salt = headerCipherKey->getSalt();
|
||||
|
||||
memcpy(&header->iv[0], &iv[0], AES_256_IV_LENGTH);
|
||||
|
||||
if (authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
if (authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
// No header 'authToken' generation needed.
|
||||
} else {
|
||||
// Populate header encryption-key details
|
||||
header->cipherHeaderDetails.encryptDomainId = headerCipherKey->getDomainId();
|
||||
header->cipherHeaderDetails.baseCipherId = headerCipherKey->getBaseCipherId();
|
||||
header->cipherHeaderDetails.salt = headerCipherKey->getSalt();
|
||||
|
||||
// Populate header authToken details
|
||||
if (header->flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
ASSERT_GE(allocSize, (bytes + finalBytes + sizeof(BlobCipherEncryptHeader)));
|
||||
ASSERT_GE(encryptBuf->getLogicalSize(), (bytes + finalBytes + sizeof(BlobCipherEncryptHeader)));
|
||||
if (header->flags.authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
ASSERT_GE(allocSize, (bytes + finalBytes));
|
||||
ASSERT_GE(encryptBuf->getLogicalSize(), (bytes + finalBytes));
|
||||
|
||||
memcpy(&ciphertext[bytes + finalBytes],
|
||||
reinterpret_cast<const uint8_t*>(header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
computeAuthToken(ciphertext,
|
||||
bytes + finalBytes + sizeof(BlobCipherEncryptHeader),
|
||||
computeAuthToken({ { ciphertext, bytes + finalBytes },
|
||||
{ reinterpret_cast<const uint8_t*>(header), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&header->singleAuthToken.authToken[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
(EncryptAuthTokenAlgo)header->flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
} else {
|
||||
ASSERT_EQ(header->flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
ASSERT_EQ(header->flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
|
||||
computeAuthToken(ciphertext,
|
||||
bytes + finalBytes,
|
||||
// TOOD: Use HMAC_SHA encyrption authentication scheme as AES_CMAC needs minimum 16 bytes cipher key
|
||||
computeAuthToken({ { ciphertext, bytes + finalBytes } },
|
||||
reinterpret_cast<const uint8_t*>(&header->cipherTextDetails.salt),
|
||||
sizeof(EncryptCipherRandomSalt),
|
||||
&header->multiAuthTokens.cipherTextAuthToken[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
computeAuthToken(reinterpret_cast<const uint8_t*>(header),
|
||||
sizeof(BlobCipherEncryptHeader),
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
computeAuthToken({ { reinterpret_cast<const uint8_t*>(header), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&header->multiAuthTokens.headerAuthToken[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
(EncryptAuthTokenAlgo)header->flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,7 +672,13 @@ Reference<EncryptBuf> EncryptBlobCipherAes265Ctr::encrypt(const uint8_t* plainte
|
|||
BlobCipherMetrics::counters(usageType).encryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(true, "Encrypting data with BlobCipher");
|
||||
CODE_PROBE(true, "BlobCipher data encryption");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"Encryption authentication disabled");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"HMAC_SHA Auth token generation");
|
||||
CODE_PROBE(header->flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"AES_CMAC Auth token generation");
|
||||
|
||||
return encryptBuf;
|
||||
}
|
||||
|
@ -677,26 +716,30 @@ void DecryptBlobCipherAes256Ctr::verifyHeaderAuthToken(const BlobCipherEncryptHe
|
|||
}
|
||||
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
ASSERT(isEncryptHeaderAuthTokenAlgoValid((EncryptAuthTokenAlgo)header.flags.authTokenAlgo));
|
||||
|
||||
BlobCipherEncryptHeader headerCopy;
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
memset(reinterpret_cast<uint8_t*>(&headerCopy.multiAuthTokens.headerAuthToken), 0, AUTH_TOKEN_SIZE);
|
||||
uint8_t computedHeaderAuthToken[AUTH_TOKEN_SIZE];
|
||||
computeAuthToken(reinterpret_cast<const uint8_t*>(&headerCopy),
|
||||
sizeof(BlobCipherEncryptHeader),
|
||||
memset(reinterpret_cast<uint8_t*>(&headerCopy.multiAuthTokens.headerAuthToken), 0, AUTH_TOKEN_MAX_SIZE);
|
||||
uint8_t computedHeaderAuthToken[AUTH_TOKEN_MAX_SIZE]{};
|
||||
computeAuthToken({ { reinterpret_cast<const uint8_t*>(&headerCopy), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&computedHeaderAuthToken[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
if (memcmp(&header.multiAuthTokens.headerAuthToken[0], &computedHeaderAuthToken[0], AUTH_TOKEN_SIZE) != 0) {
|
||||
(EncryptAuthTokenAlgo)header.flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
|
||||
int authTokenSize = getEncryptHeaderAuthTokenSize(header.flags.authTokenAlgo);
|
||||
ASSERT_LE(authTokenSize, AUTH_TOKEN_MAX_SIZE);
|
||||
if (memcmp(&header.multiAuthTokens.headerAuthToken[0], &computedHeaderAuthToken[0], authTokenSize) != 0) {
|
||||
TraceEvent(SevWarn, "BlobCipherVerifyEncryptBlobHeaderAuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("MultiAuthHeaderAuthToken",
|
||||
StringRef(arena, &header.multiAuthTokens.headerAuthToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedHeaderAuthToken", StringRef(computedHeaderAuthToken, AUTH_TOKEN_SIZE));
|
||||
StringRef(arena, &header.multiAuthTokens.headerAuthToken[0], AUTH_TOKEN_MAX_SIZE).toString())
|
||||
.detail("ComputedHeaderAuthToken", StringRef(computedHeaderAuthToken, AUTH_TOKEN_MAX_SIZE));
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
|
||||
|
@ -706,32 +749,35 @@ void DecryptBlobCipherAes256Ctr::verifyHeaderAuthToken(const BlobCipherEncryptHe
|
|||
void DecryptBlobCipherAes256Ctr::verifyHeaderSingleAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
// Header authToken not set for single auth-token mode.
|
||||
ASSERT(!headerAuthTokenValidationDone);
|
||||
|
||||
// prepare the payload {cipherText + encryptionHeader}
|
||||
memcpy(&buff[0], ciphertext, ciphertextLen);
|
||||
memcpy(&buff[ciphertextLen], reinterpret_cast<const uint8_t*>(&header), sizeof(BlobCipherEncryptHeader));
|
||||
// ensure the 'authToken' is reset before computing the 'authentication token'
|
||||
BlobCipherEncryptHeader* eHeader = (BlobCipherEncryptHeader*)(&buff[ciphertextLen]);
|
||||
memset(reinterpret_cast<uint8_t*>(&eHeader->singleAuthToken), 0, 2 * AUTH_TOKEN_SIZE);
|
||||
|
||||
uint8_t computed[AUTH_TOKEN_SIZE];
|
||||
computeAuthToken(buff,
|
||||
ciphertextLen + sizeof(BlobCipherEncryptHeader),
|
||||
BlobCipherEncryptHeader headerCopy;
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
memset(reinterpret_cast<uint8_t*>(&headerCopy.singleAuthToken), 0, 2 * AUTH_TOKEN_MAX_SIZE);
|
||||
uint8_t computed[AUTH_TOKEN_MAX_SIZE];
|
||||
computeAuthToken({ { ciphertext, ciphertextLen },
|
||||
{ reinterpret_cast<const uint8_t*>(&headerCopy), sizeof(BlobCipherEncryptHeader) } },
|
||||
headerCipherKey->rawCipher(),
|
||||
AES_256_KEY_LENGTH,
|
||||
&computed[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
if (memcmp(&header.singleAuthToken.authToken[0], &computed[0], AUTH_TOKEN_SIZE) != 0) {
|
||||
(EncryptAuthTokenAlgo)header.flags.authTokenAlgo,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
|
||||
int authTokenSize = getEncryptHeaderAuthTokenSize(header.flags.authTokenAlgo);
|
||||
ASSERT_LE(authTokenSize, AUTH_TOKEN_MAX_SIZE);
|
||||
if (memcmp(&header.singleAuthToken.authToken[0], &computed[0], authTokenSize) != 0) {
|
||||
TraceEvent(SevWarn, "BlobCipherVerifyEncryptBlobHeaderAuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("SingleAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedSingleAuthToken", StringRef(computed, AUTH_TOKEN_SIZE));
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_MAX_SIZE).toString())
|
||||
.detail("ComputedSingleAuthToken", StringRef(computed, AUTH_TOKEN_MAX_SIZE));
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
}
|
||||
|
@ -739,25 +785,26 @@ void DecryptBlobCipherAes256Ctr::verifyHeaderSingleAuthToken(const uint8_t* ciph
|
|||
void DecryptBlobCipherAes256Ctr::verifyHeaderMultiAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
if (!headerAuthTokenValidationDone) {
|
||||
verifyHeaderAuthToken(header, arena);
|
||||
}
|
||||
uint8_t computedCipherTextAuthToken[AUTH_TOKEN_SIZE];
|
||||
computeAuthToken(ciphertext,
|
||||
ciphertextLen,
|
||||
uint8_t computedCipherTextAuthToken[AUTH_TOKEN_MAX_SIZE];
|
||||
// TOOD: Use HMAC_SHA encyrption authentication scheme as AES_CMAC needs minimum 16 bytes cipher key
|
||||
computeAuthToken({ { ciphertext, ciphertextLen } },
|
||||
reinterpret_cast<const uint8_t*>(&header.cipherTextDetails.salt),
|
||||
sizeof(EncryptCipherRandomSalt),
|
||||
&computedCipherTextAuthToken[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
if (memcmp(&header.multiAuthTokens.cipherTextAuthToken[0], &computedCipherTextAuthToken[0], AUTH_TOKEN_SIZE) != 0) {
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
AUTH_TOKEN_MAX_SIZE);
|
||||
if (memcmp(&header.multiAuthTokens.cipherTextAuthToken[0], &computedCipherTextAuthToken[0], AUTH_TOKEN_MAX_SIZE) !=
|
||||
0) {
|
||||
TraceEvent(SevWarn, "BlobCipherVerifyEncryptBlobHeaderAuthTokenMismatch")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderMode", header.flags.encryptMode)
|
||||
.detail("MultiAuthCipherTextAuthToken",
|
||||
StringRef(arena, &header.multiAuthTokens.cipherTextAuthToken[0], AUTH_TOKEN_SIZE).toString())
|
||||
.detail("ComputedCipherTextAuthToken", StringRef(computedCipherTextAuthToken, AUTH_TOKEN_SIZE));
|
||||
StringRef(arena, &header.multiAuthTokens.cipherTextAuthToken[0], AUTH_TOKEN_MAX_SIZE).toString())
|
||||
.detail("ComputedCipherTextAuthToken", StringRef(computedCipherTextAuthToken, AUTH_TOKEN_MAX_SIZE));
|
||||
throw encrypt_header_authtoken_mismatch();
|
||||
}
|
||||
}
|
||||
|
@ -765,13 +812,12 @@ void DecryptBlobCipherAes256Ctr::verifyHeaderMultiAuthToken(const uint8_t* ciphe
|
|||
void DecryptBlobCipherAes256Ctr::verifyAuthTokens(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena) {
|
||||
if (header.flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
verifyHeaderSingleAuthToken(ciphertext, ciphertextLen, header, buff, arena);
|
||||
if (header.flags.authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE) {
|
||||
verifyHeaderSingleAuthToken(ciphertext, ciphertextLen, header, arena);
|
||||
} else {
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
verifyHeaderMultiAuthToken(ciphertext, ciphertextLen, header, buff, arena);
|
||||
verifyHeaderMultiAuthToken(ciphertext, ciphertextLen, header, arena);
|
||||
}
|
||||
|
||||
authTokensValidationDone = true;
|
||||
|
@ -780,13 +826,13 @@ void DecryptBlobCipherAes256Ctr::verifyAuthTokens(const uint8_t* ciphertext,
|
|||
void DecryptBlobCipherAes256Ctr::verifyEncryptHeaderMetadata(const BlobCipherEncryptHeader& header) {
|
||||
// validate header flag sanity
|
||||
if (header.flags.headerVersion != EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION ||
|
||||
header.flags.encryptMode != ENCRYPT_CIPHER_MODE_AES_256_CTR ||
|
||||
header.flags.encryptMode != EncryptCipherMode::ENCRYPT_CIPHER_MODE_AES_256_CTR ||
|
||||
!isEncryptHeaderAuthTokenModeValid((EncryptAuthTokenMode)header.flags.authTokenMode)) {
|
||||
TraceEvent(SevWarn, "BlobCipherVerifyEncryptBlobHeader")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("ExpectedVersion", EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION)
|
||||
.detail("EncryptCipherMode", header.flags.encryptMode)
|
||||
.detail("ExpectedCipherMode", ENCRYPT_CIPHER_MODE_AES_256_CTR)
|
||||
.detail("ExpectedCipherMode", EncryptCipherMode::ENCRYPT_CIPHER_MODE_AES_256_CTR)
|
||||
.detail("EncryptHeaderAuthTokenMode", header.flags.authTokenMode);
|
||||
throw encrypt_header_metadata_mismatch();
|
||||
}
|
||||
|
@ -803,19 +849,18 @@ Reference<EncryptBuf> DecryptBlobCipherAes256Ctr::decrypt(const uint8_t* ciphert
|
|||
|
||||
verifyEncryptHeaderMetadata(header);
|
||||
|
||||
if (header.flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && !headerCipherKey.isValid()) {
|
||||
if (header.flags.authTokenMode != EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
|
||||
!headerCipherKey.isValid()) {
|
||||
TraceEvent(SevWarn, "BlobCipherDecryptInvalidHeaderCipherKey")
|
||||
.detail("AuthTokenMode", header.flags.authTokenMode);
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
const int allocSize = header.flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
? ciphertextLen + AES_BLOCK_SIZE + sizeof(BlobCipherEncryptHeader)
|
||||
: ciphertextLen + AES_BLOCK_SIZE;
|
||||
const int allocSize = ciphertextLen + AES_BLOCK_SIZE;
|
||||
Reference<EncryptBuf> decrypted = makeReference<EncryptBuf>(allocSize, arena);
|
||||
|
||||
if (header.flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
verifyAuthTokens(ciphertext, ciphertextLen, header, decrypted->begin(), arena);
|
||||
if (header.flags.authTokenMode != EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
verifyAuthTokens(ciphertext, ciphertextLen, header, arena);
|
||||
ASSERT(authTokensValidationDone);
|
||||
}
|
||||
|
||||
|
@ -849,7 +894,13 @@ Reference<EncryptBuf> DecryptBlobCipherAes256Ctr::decrypt(const uint8_t* ciphert
|
|||
BlobCipherMetrics::counters(usageType).decryptCPUTimeNS += int64_t((timer_monotonic() - startTime) * 1e9);
|
||||
}
|
||||
|
||||
CODE_PROBE(true, "Decrypting data with BlobCipher");
|
||||
CODE_PROBE(true, "BlobCipher data decryption");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
"Decryption authentication disabled");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
"Decryption HMAC_SHA Auth token verification");
|
||||
CODE_PROBE(header.flags.authTokenAlgo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
"Decryption AES_CMAC Auth token verification");
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
@ -874,14 +925,15 @@ HmacSha256DigestGen::~HmacSha256DigestGen() {
|
|||
}
|
||||
}
|
||||
|
||||
unsigned int HmacSha256DigestGen::digest(const unsigned char* data,
|
||||
size_t len,
|
||||
unsigned int HmacSha256DigestGen::digest(const std::vector<std::pair<const uint8_t*, size_t>>& payload,
|
||||
unsigned char* buf,
|
||||
unsigned int bufLen) {
|
||||
ASSERT_EQ(bufLen, HMAC_size(ctx));
|
||||
|
||||
if (HMAC_Update(ctx, data, len) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
for (const auto& p : payload) {
|
||||
if (HMAC_Update(ctx, p.first, p.second) != 1) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int digestLen = 0;
|
||||
|
@ -889,23 +941,84 @@ unsigned int HmacSha256DigestGen::digest(const unsigned char* data,
|
|||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
CODE_PROBE(true, "Digest generation");
|
||||
CODE_PROBE(true, "HMAC_SHA Digest generation");
|
||||
|
||||
return digestLen;
|
||||
}
|
||||
|
||||
void computeAuthToken(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
// Aes256CtrCmacDigestGen methods
|
||||
Aes256CmacDigestGen::Aes256CmacDigestGen(const unsigned char* key, size_t keylen) : ctx(CMAC_CTX_new()) {
|
||||
ASSERT_EQ(keylen, AES_256_KEY_LENGTH);
|
||||
|
||||
if (ctx == nullptr) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
if (!CMAC_Init(ctx, key, keylen, EVP_aes_256_cbc(), NULL)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
|
||||
size_t Aes256CmacDigestGen::digest(const std::vector<std::pair<const uint8_t*, size_t>>& payload,
|
||||
uint8_t* digest,
|
||||
int digestlen) {
|
||||
ASSERT(ctx != nullptr);
|
||||
ASSERT_GE(digestlen, AUTH_TOKEN_AES_CMAC_SIZE);
|
||||
|
||||
for (const auto& p : payload) {
|
||||
if (!CMAC_Update(ctx, p.first, p.second)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
}
|
||||
size_t ret;
|
||||
if (!CMAC_Final(ctx, digest, &ret)) {
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Aes256CmacDigestGen::~Aes256CmacDigestGen() {
|
||||
if (ctx != nullptr) {
|
||||
CMAC_CTX_free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void computeAuthToken(const std::vector<std::pair<const uint8_t*, size_t>>& payload,
|
||||
const uint8_t* key,
|
||||
const int keyLen,
|
||||
unsigned char* digestBuf,
|
||||
unsigned int digestBufSz) {
|
||||
HmacSha256DigestGen hmacGenerator(key, keyLen);
|
||||
unsigned int digestLen = hmacGenerator.digest(payload, payloadLen, digestBuf, digestBufSz);
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
unsigned int digestBufMaxSz) {
|
||||
ASSERT_EQ(digestBufMaxSz, AUTH_TOKEN_MAX_SIZE);
|
||||
ASSERT(isEncryptHeaderAuthTokenAlgoValid(algo));
|
||||
|
||||
ASSERT_EQ(digestLen, digestBufSz);
|
||||
int authTokenSz = getEncryptHeaderAuthTokenSize(algo);
|
||||
ASSERT_LE(authTokenSz, AUTH_TOKEN_MAX_SIZE);
|
||||
|
||||
CODE_PROBE(true, "Auth token generation");
|
||||
if (algo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA) {
|
||||
ASSERT_EQ(authTokenSz, AUTH_TOKEN_HMAC_SHA_SIZE);
|
||||
|
||||
HmacSha256DigestGen hmacGenerator(key, keyLen);
|
||||
unsigned int digestLen = hmacGenerator.digest(payload, digestBuf, authTokenSz);
|
||||
|
||||
ASSERT_EQ(digestLen, authTokenSz);
|
||||
} else if (algo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC) {
|
||||
ASSERT_EQ(authTokenSz, AUTH_TOKEN_AES_CMAC_SIZE);
|
||||
ASSERT_EQ(keyLen, AES_256_KEY_LENGTH);
|
||||
|
||||
Aes256CmacDigestGen cmacGenerator(key, keyLen);
|
||||
size_t digestLen = cmacGenerator.digest(payload, digestBuf, authTokenSz);
|
||||
|
||||
ASSERT_EQ(digestLen, authTokenSz);
|
||||
} else {
|
||||
throw not_implemented();
|
||||
}
|
||||
}
|
||||
|
||||
EncryptAuthTokenMode getEncryptAuthTokenMode(const EncryptAuthTokenMode mode) {
|
||||
// Override mode if authToken isn't enabled
|
||||
return FLOW_KNOBS->ENCRYPT_HEADER_AUTH_TOKEN_ENABLED ? mode
|
||||
: EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE;
|
||||
}
|
||||
|
||||
// Only used to link unit tests
|
||||
|
@ -1075,13 +1188,13 @@ TEST_CASE("flow/BlobCipher") {
|
|||
BlobCipherEncryptHeader headerCopy;
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_MODE_NONE
|
||||
{
|
||||
TraceEvent("NoneAuthModeStart").log();
|
||||
TraceEvent("NoneAuthModeStart");
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
Reference<BlobCipherKey>(),
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
@ -1089,12 +1202,14 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE);
|
||||
ASSERT_EQ(header.flags.encryptMode, EncryptCipherMode::ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("HeaderEncryptAuthTokenMode", header.flags.authTokenMode)
|
||||
.detail("HeaderEncryptAuthTokenAlgo", header.flags.authTokenAlgo)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId);
|
||||
|
||||
|
@ -1109,7 +1224,7 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTestDecryptDone").log();
|
||||
TraceEvent("BlobCipherTestDecryptDone");
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
|
@ -1160,18 +1275,20 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT(false);
|
||||
}
|
||||
|
||||
TraceEvent("NoneAuthMode.Done").log();
|
||||
TraceEvent("NoneAuthModeDone");
|
||||
}
|
||||
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_TOKEN_MODE_SINGLE
|
||||
// HMAC_SHA authToken algorithm
|
||||
{
|
||||
TraceEvent("SingleAuthMode.Start").log();
|
||||
TraceEvent("SingleAuthModeHmacShaStart").log();
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
@ -1180,15 +1297,130 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
ASSERT_EQ(header.flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
ASSERT_EQ(header.flags.authTokenAlgo, EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("HeaderEncryptAuthTokenMode", header.flags.authTokenMode)
|
||||
.detail("HeaderEncryptAuthTokenAlgo", header.flags.authTokenAlgo)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString());
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_HMAC_SHA_SIZE).toString());
|
||||
|
||||
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);
|
||||
ASSERT(tCipherKeyKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTestDecryptDone");
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.headerVersion += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.encryptMode += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_HMAC_SHA_SIZE - 1);
|
||||
headerCopy.singleAuthToken.authToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encrypted buffer payload corruption
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
uint8_t temp[bufLen];
|
||||
memcpy(encrypted->begin(), &temp[0], bufLen);
|
||||
int tIdx = deterministicRandom()->randomInt(0, bufLen - 1);
|
||||
temp[tIdx] += 1;
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(&temp[0], bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("SingleAuthModeHmacShaDone");
|
||||
}
|
||||
// AES_CMAC authToken algorithm
|
||||
{
|
||||
TraceEvent("SingleAuthModeAesCMacStart").log();
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE);
|
||||
ASSERT_EQ(header.flags.authTokenAlgo, EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("HeaderEncryptAuthTokenMode", header.flags.authTokenMode)
|
||||
.detail("HeaderEncryptAuthTokenAlgo", header.flags.authTokenAlgo)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_AES_CMAC_SIZE).toString());
|
||||
|
||||
Reference<BlobCipherKey> tCipherKeyKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId,
|
||||
|
@ -1242,7 +1474,7 @@ TEST_CASE("flow/BlobCipher") {
|
|||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_AES_CMAC_SIZE - 1);
|
||||
headerCopy.singleAuthToken.authToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKeyKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
|
@ -1269,18 +1501,20 @@ TEST_CASE("flow/BlobCipher") {
|
|||
}
|
||||
}
|
||||
|
||||
TraceEvent("SingleAuthModeDone").log();
|
||||
TraceEvent("SingleAuthModeAesCmacDone");
|
||||
}
|
||||
|
||||
// validate basic encrypt followed by decrypt operation for AUTH_TOKEN_MODE_MULTI
|
||||
// HMAC_SHA authToken algorithm
|
||||
{
|
||||
TraceEvent("MultiAuthModeStart").log();
|
||||
TraceEvent("MultiAuthModeHmacShaStart").log();
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI,
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
@ -1290,14 +1524,17 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
ASSERT_EQ(header.flags.authTokenAlgo, EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("HeaderEncryptAuthTokenMode", header.flags.authTokenMode)
|
||||
.detail("HeaderEncryptAuthTokenAlgo", header.flags.authTokenAlgo)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_SIZE).toString());
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_HMAC_SHA_SIZE).toString());
|
||||
|
||||
Reference<BlobCipherKey> tCipherKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId,
|
||||
header.cipherTextDetails.baseCipherId,
|
||||
|
@ -1352,7 +1589,7 @@ TEST_CASE("flow/BlobCipher") {
|
|||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_HMAC_SHA_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.cipherTextAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
|
@ -1369,7 +1606,7 @@ TEST_CASE("flow/BlobCipher") {
|
|||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_SIZE - 1);
|
||||
hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_HMAC_SHA_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.headerAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
|
@ -1395,7 +1632,136 @@ TEST_CASE("flow/BlobCipher") {
|
|||
}
|
||||
}
|
||||
|
||||
TraceEvent("MultiAuthModeDone").log();
|
||||
TraceEvent("MultiAuthModeHmacShaDone");
|
||||
}
|
||||
// AES_CMAC authToken algorithm
|
||||
{
|
||||
TraceEvent("MultiAuthModeAesCmacStart");
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(cipherKey,
|
||||
headerCipherKey,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI,
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC,
|
||||
BlobCipherMetrics::TEST);
|
||||
BlobCipherEncryptHeader header;
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
|
||||
ASSERT_EQ(encrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_NE(memcmp(&orgData[0], encrypted->begin(), bufLen), 0);
|
||||
ASSERT_EQ(header.flags.headerVersion, EncryptBlobCipherAes265Ctr::ENCRYPT_HEADER_VERSION);
|
||||
ASSERT_EQ(header.flags.encryptMode, ENCRYPT_CIPHER_MODE_AES_256_CTR);
|
||||
ASSERT_EQ(header.flags.authTokenMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI);
|
||||
ASSERT_EQ(header.flags.authTokenAlgo, EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC);
|
||||
|
||||
TraceEvent("BlobCipherTestEncryptDone")
|
||||
.detail("HeaderVersion", header.flags.headerVersion)
|
||||
.detail("HeaderEncryptMode", header.flags.encryptMode)
|
||||
.detail("HeaderEncryptAuthTokenMode", header.flags.authTokenMode)
|
||||
.detail("HeaderEncryptAuthTokenAlgo", header.flags.authTokenAlgo)
|
||||
.detail("DomainId", header.cipherTextDetails.encryptDomainId)
|
||||
.detail("BaseCipherId", header.cipherTextDetails.baseCipherId)
|
||||
.detail("HeaderAuthToken",
|
||||
StringRef(arena, &header.singleAuthToken.authToken[0], AUTH_TOKEN_AES_CMAC_SIZE).toString());
|
||||
|
||||
Reference<BlobCipherKey> tCipherKey = 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);
|
||||
|
||||
ASSERT(tCipherKey->isEqual(cipherKey));
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
Reference<EncryptBuf> decrypted = decryptor.decrypt(encrypted->begin(), bufLen, header, arena);
|
||||
|
||||
ASSERT_EQ(decrypted->getLogicalSize(), bufLen);
|
||||
ASSERT_EQ(memcmp(decrypted->begin(), &orgData[0], bufLen), 0);
|
||||
|
||||
TraceEvent("BlobCipherTestDecryptDone").log();
|
||||
|
||||
// induce encryption header corruption - headerVersion corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.headerVersion += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - encryptionMode corrupted
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
headerCopy.flags.encryptMode += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_metadata_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - cipherText authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
int hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_AES_CMAC_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.cipherTextAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// induce encryption header corruption - header authToken mismatch
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
memcpy(reinterpret_cast<uint8_t*>(&headerCopy),
|
||||
reinterpret_cast<const uint8_t*>(&header),
|
||||
sizeof(BlobCipherEncryptHeader));
|
||||
hIdx = deterministicRandom()->randomInt(0, AUTH_TOKEN_AES_CMAC_SIZE - 1);
|
||||
headerCopy.multiAuthTokens.headerAuthToken[hIdx] += 1;
|
||||
try {
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(encrypted->begin(), bufLen, headerCopy, arena);
|
||||
ASSERT(false); // error expected
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
encrypted = encryptor.encrypt(&orgData[0], bufLen, &header, arena);
|
||||
uint8_t temp[bufLen];
|
||||
memcpy(encrypted->begin(), &temp[0], bufLen);
|
||||
int tIdx = deterministicRandom()->randomInt(0, bufLen - 1);
|
||||
temp[tIdx] += 1;
|
||||
DecryptBlobCipherAes256Ctr decryptor(tCipherKey, hCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
decrypted = decryptor.decrypt(&temp[0], bufLen, header, arena);
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_encrypt_header_authtoken_mismatch) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("MultiAuthModeAesCmacDone");
|
||||
}
|
||||
|
||||
// Validate dropping encryptDomainId cached keys
|
||||
|
@ -1411,6 +1777,6 @@ TEST_CASE("flow/BlobCipher") {
|
|||
ASSERT(cachedKeys.empty());
|
||||
}
|
||||
|
||||
TraceEvent("BlobCipherTestDone").log();
|
||||
TraceEvent("BlobCipherTestDone");
|
||||
return Void();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
#include "flow/Arena.h"
|
||||
#include "flow/CompressionUtils.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/serialize.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
@ -285,12 +287,13 @@ struct IndexBlockRef {
|
|||
TraceEvent(SevDebug, "IndexBlockEncrypt_Before").detail("Chksum", chksum);
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(eKeys.textCipherKey,
|
||||
eKeys.headerCipherKey,
|
||||
cipherKeysCtx.ivRef.begin(),
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
BlobCipherMetrics::BLOB_GRANULE);
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
eKeys.textCipherKey,
|
||||
eKeys.headerCipherKey,
|
||||
cipherKeysCtx.ivRef.begin(),
|
||||
AES_256_IV_LENGTH,
|
||||
getEncryptAuthTokenMode(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
BlobCipherMetrics::BLOB_GRANULE);
|
||||
Value serializedBuff = ObjectWriter::toValue(block, IncludeVersion(ProtocolVersion::withBlobGranuleFile()));
|
||||
BlobCipherEncryptHeader header;
|
||||
buffer = encryptor.encrypt(serializedBuff.contents().begin(), serializedBuff.contents().size(), &header, arena)
|
||||
|
@ -408,12 +411,13 @@ struct IndexBlobGranuleFileChunkRef {
|
|||
TraceEvent(SevDebug, "BlobChunkEncrypt_Before").detail("Chksum", chksum);
|
||||
}
|
||||
|
||||
EncryptBlobCipherAes265Ctr encryptor(eKeys.textCipherKey,
|
||||
eKeys.headerCipherKey,
|
||||
cipherKeysCtx.ivRef.begin(),
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
BlobCipherMetrics::BLOB_GRANULE);
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
eKeys.textCipherKey,
|
||||
eKeys.headerCipherKey,
|
||||
cipherKeysCtx.ivRef.begin(),
|
||||
AES_256_IV_LENGTH,
|
||||
getEncryptAuthTokenMode(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
BlobCipherMetrics::BLOB_GRANULE);
|
||||
BlobCipherEncryptHeader header;
|
||||
chunkRef.buffer =
|
||||
encryptor.encrypt(chunkRef.buffer.begin(), chunkRef.buffer.size(), &header, arena)->toStringRef();
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <limits>
|
||||
#include <memory>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/cmac.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
@ -192,7 +193,8 @@ typedef struct BlobCipherEncryptHeader {
|
|||
uint8_t headerVersion{};
|
||||
uint8_t encryptMode{};
|
||||
uint8_t authTokenMode{};
|
||||
uint8_t _reserved[4]{};
|
||||
uint8_t authTokenAlgo{};
|
||||
uint8_t _reserved[3]{};
|
||||
} flags;
|
||||
uint64_t _padding{};
|
||||
};
|
||||
|
@ -223,12 +225,12 @@ typedef struct BlobCipherEncryptHeader {
|
|||
|
||||
struct {
|
||||
// Cipher text authentication token
|
||||
uint8_t cipherTextAuthToken[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t headerAuthToken[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t cipherTextAuthToken[AUTH_TOKEN_MAX_SIZE]{};
|
||||
uint8_t headerAuthToken[AUTH_TOKEN_MAX_SIZE]{};
|
||||
} multiAuthTokens;
|
||||
struct {
|
||||
uint8_t authToken[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t _reserved[AUTH_TOKEN_SIZE]{};
|
||||
uint8_t authToken[AUTH_TOKEN_MAX_SIZE]{};
|
||||
uint8_t _reserved[AUTH_TOKEN_MAX_SIZE]{};
|
||||
} singleAuthToken;
|
||||
};
|
||||
|
||||
|
@ -555,7 +557,19 @@ public:
|
|||
BlobCipherMetrics::UsageType usageType);
|
||||
EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const uint8_t* iv,
|
||||
const int ivLen,
|
||||
const EncryptAuthTokenMode mode,
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
BlobCipherMetrics::UsageType usageType);
|
||||
EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const EncryptAuthTokenMode mode,
|
||||
BlobCipherMetrics::UsageType usageType);
|
||||
EncryptBlobCipherAes265Ctr(Reference<BlobCipherKey> tCipherKey,
|
||||
Reference<BlobCipherKey> hCipherKey,
|
||||
const EncryptAuthTokenMode mode,
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
BlobCipherMetrics::UsageType usageType);
|
||||
~EncryptBlobCipherAes265Ctr();
|
||||
|
||||
|
@ -571,6 +585,7 @@ private:
|
|||
EncryptAuthTokenMode authTokenMode;
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
BlobCipherMetrics::UsageType usageType;
|
||||
EncryptAuthTokenAlgo authTokenAlgo;
|
||||
|
||||
void init();
|
||||
};
|
||||
|
@ -608,17 +623,14 @@ private:
|
|||
void verifyAuthTokens(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
void verifyHeaderSingleAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
void verifyHeaderMultiAuthToken(const uint8_t* ciphertext,
|
||||
const int ciphertextLen,
|
||||
const BlobCipherEncryptHeader& header,
|
||||
uint8_t* buff,
|
||||
Arena& arena);
|
||||
};
|
||||
|
||||
|
@ -627,17 +639,32 @@ public:
|
|||
HmacSha256DigestGen(const unsigned char* key, size_t len);
|
||||
~HmacSha256DigestGen();
|
||||
HMAC_CTX* getCtx() const { return ctx; }
|
||||
unsigned int digest(unsigned char const* data, size_t len, unsigned char* buf, unsigned int bufLen);
|
||||
unsigned int digest(const std::vector<std::pair<const uint8_t*, size_t>>& payload,
|
||||
unsigned char* buf,
|
||||
unsigned int bufLen);
|
||||
|
||||
private:
|
||||
HMAC_CTX* ctx;
|
||||
};
|
||||
|
||||
void computeAuthToken(const uint8_t* payload,
|
||||
const int payloadLen,
|
||||
class Aes256CmacDigestGen final : NonCopyable {
|
||||
public:
|
||||
Aes256CmacDigestGen(const unsigned char* key, size_t len);
|
||||
~Aes256CmacDigestGen();
|
||||
CMAC_CTX* getCtx() const { return ctx; }
|
||||
size_t digest(const std::vector<std::pair<const uint8_t*, size_t>>& payload, uint8_t* digest, int digestlen);
|
||||
|
||||
private:
|
||||
CMAC_CTX* ctx;
|
||||
};
|
||||
|
||||
void computeAuthToken(const std::vector<std::pair<const uint8_t*, size_t>>& payload,
|
||||
const uint8_t* key,
|
||||
const int keyLen,
|
||||
unsigned char* digestBuf,
|
||||
unsigned int digestBufSz);
|
||||
const EncryptAuthTokenAlgo algo,
|
||||
unsigned int digestMaxBufSz);
|
||||
|
||||
EncryptAuthTokenMode getEncryptAuthTokenMode(const EncryptAuthTokenMode mode);
|
||||
|
||||
#endif // FDBCLIENT_BLOB_CIPHER_H
|
|
@ -27,6 +27,8 @@
|
|||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Knobs.h"
|
||||
#include "fdbclient/Tracing.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/Knobs.h"
|
||||
|
||||
// The versioned message has wire format : -1, version, messages
|
||||
static const int32_t VERSION_HEADER = -1;
|
||||
|
@ -153,12 +155,13 @@ struct MutationRef {
|
|||
deterministicRandom()->randomBytes(iv, AES_256_IV_LENGTH);
|
||||
BinaryWriter bw(AssumeVersion(ProtocolVersion::withEncryptionAtRest()));
|
||||
bw << *this;
|
||||
EncryptBlobCipherAes265Ctr cipher(textCipherItr->second,
|
||||
headerCipherItr->second,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
usageType);
|
||||
EncryptBlobCipherAes265Ctr cipher(
|
||||
textCipherItr->second,
|
||||
headerCipherItr->second,
|
||||
iv,
|
||||
AES_256_IV_LENGTH,
|
||||
getEncryptAuthTokenMode(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
usageType);
|
||||
BlobCipherEncryptHeader* header = new (arena) BlobCipherEncryptHeader;
|
||||
StringRef headerRef(reinterpret_cast<const uint8_t*>(header), sizeof(BlobCipherEncryptHeader));
|
||||
StringRef payload =
|
||||
|
|
|
@ -411,7 +411,7 @@ ACTOR Future<Void> getCipherKeysByBaseCipherKeyIds(Reference<EncryptKeyProxyData
|
|||
|
||||
const auto itr = lookupCipherInfoMap.find(std::make_pair(item.encryptDomainId, item.encryptKeyId));
|
||||
if (itr == lookupCipherInfoMap.end()) {
|
||||
TraceEvent(SevError, "GetCipherKeysByKeyIds_MappingNotFound", ekpProxyData->myId)
|
||||
TraceEvent(SevError, "GetCipherKeysByKeyIdsMappingNotFound", ekpProxyData->myId)
|
||||
.detail("DomainId", item.encryptDomainId);
|
||||
throw encrypt_keys_fetch_failed();
|
||||
}
|
||||
|
@ -546,7 +546,7 @@ ACTOR Future<Void> getLatestCipherKeys(Reference<EncryptKeyProxyData> ekpProxyDa
|
|||
// Record the fetched cipher details to the local cache for the future references
|
||||
const auto itr = lookupCipherDomains.find(item.encryptDomainId);
|
||||
if (itr == lookupCipherDomains.end()) {
|
||||
TraceEvent(SevError, "GetLatestCipherKeys_DomainIdNotFound", ekpProxyData->myId)
|
||||
TraceEvent(SevError, "GetLatestCipherKeysDomainIdNotFound", ekpProxyData->myId)
|
||||
.detail("DomainId", item.encryptDomainId);
|
||||
throw encrypt_keys_fetch_failed();
|
||||
}
|
||||
|
@ -602,7 +602,7 @@ ACTOR Future<Void> refreshEncryptionKeysCore(Reference<EncryptKeyProxyData> ekpP
|
|||
KmsConnectorInterface kmsConnectorInf) {
|
||||
state UID debugId = deterministicRandom()->randomUniqueID();
|
||||
|
||||
state TraceEvent t("RefreshEKs_Start", ekpProxyData->myId);
|
||||
state TraceEvent t("RefreshEKsStart", ekpProxyData->myId);
|
||||
t.setMaxEventLength(SERVER_KNOBS->ENCRYPT_PROXY_MAX_DBG_TRACE_LENGTH);
|
||||
t.detail("KmsConnInf", kmsConnectorInf.id());
|
||||
t.detail("DebugId", debugId);
|
||||
|
@ -634,7 +634,7 @@ ACTOR Future<Void> refreshEncryptionKeysCore(Reference<EncryptKeyProxyData> ekpP
|
|||
for (const auto& item : rep.cipherKeyDetails) {
|
||||
const auto itr = ekpProxyData->baseCipherDomainIdCache.find(item.encryptDomainId);
|
||||
if (itr == ekpProxyData->baseCipherDomainIdCache.end()) {
|
||||
TraceEvent(SevInfo, "RefreshEKs_DomainIdNotFound", ekpProxyData->myId)
|
||||
TraceEvent(SevInfo, "RefreshEKsDomainIdNotFound", ekpProxyData->myId)
|
||||
.detail("DomainId", item.encryptDomainId);
|
||||
// Continue updating the cache with other elements
|
||||
continue;
|
||||
|
@ -662,7 +662,7 @@ ACTOR Future<Void> refreshEncryptionKeysCore(Reference<EncryptKeyProxyData> ekpP
|
|||
t.detail("NumKeys", rep.cipherKeyDetails.size());
|
||||
} catch (Error& e) {
|
||||
if (!canReplyWith(e)) {
|
||||
TraceEvent(SevWarn, "RefreshEKs_Error").error(e);
|
||||
TraceEvent(SevWarn, "RefreshEKsError").error(e);
|
||||
throw e;
|
||||
}
|
||||
TraceEvent("RefreshEKs").detail("ErrorCode", e.code());
|
||||
|
@ -828,7 +828,7 @@ ACTOR Future<Void> encryptKeyProxyServer(EncryptKeyProxyInterface ekpInterface,
|
|||
state KmsConnectorInterface kmsConnectorInf;
|
||||
kmsConnectorInf.initEndpoints();
|
||||
|
||||
TraceEvent("EKP_Start", self->myId).detail("KmsConnectorInf", kmsConnectorInf.id());
|
||||
TraceEvent("EKPStart", self->myId).detail("KmsConnectorInf", kmsConnectorInf.id());
|
||||
|
||||
activateKmsConnector(self, kmsConnectorInf);
|
||||
|
||||
|
@ -861,7 +861,7 @@ ACTOR Future<Void> encryptKeyProxyServer(EncryptKeyProxyInterface ekpInterface,
|
|||
self->addActor.send(getLatestBlobMetadata(self, kmsConnectorInf, req));
|
||||
}
|
||||
when(HaltEncryptKeyProxyRequest req = waitNext(ekpInterface.haltEncryptKeyProxy.getFuture())) {
|
||||
TraceEvent("EKP_Halted", self->myId).detail("ReqID", req.requesterID);
|
||||
TraceEvent("EKPHalted", self->myId).detail("ReqID", req.requesterID);
|
||||
req.reply.send(Void());
|
||||
break;
|
||||
}
|
||||
|
@ -871,7 +871,7 @@ ACTOR Future<Void> encryptKeyProxyServer(EncryptKeyProxyInterface ekpInterface,
|
|||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
TraceEvent("EKP_Terminated", self->myId).errorUnsuppressed(e);
|
||||
TraceEvent("EKPTerminated", self->myId).errorUnsuppressed(e);
|
||||
}
|
||||
|
||||
return Void();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "fdbserver/IKeyValueStore.h"
|
||||
#include "fdbserver/RadixTree.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
|
@ -489,10 +490,11 @@ private:
|
|||
|
||||
ASSERT(cipherKeys.cipherTextKey.isValid());
|
||||
ASSERT(cipherKeys.cipherHeaderKey.isValid());
|
||||
EncryptBlobCipherAes265Ctr cipher(cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
BlobCipherMetrics::KV_MEMORY);
|
||||
EncryptBlobCipherAes265Ctr cipher(
|
||||
cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
getEncryptAuthTokenMode(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
BlobCipherMetrics::KV_MEMORY);
|
||||
BlobCipherEncryptHeader cipherHeader;
|
||||
Arena arena;
|
||||
StringRef ciphertext =
|
||||
|
|
|
@ -66,13 +66,13 @@ struct SimKmsConnectorContext : NonCopyable, ReferenceCounted<SimKmsConnectorCon
|
|||
// Construct encryption keyStore.
|
||||
// Note the keys generated must be the same after restart.
|
||||
for (int i = 1; i <= maxEncryptionKeys; i++) {
|
||||
uint8_t digest[AUTH_TOKEN_SIZE];
|
||||
computeAuthToken(reinterpret_cast<const unsigned char*>(&i),
|
||||
sizeof(i),
|
||||
uint8_t digest[AUTH_TOKEN_HMAC_SHA_SIZE];
|
||||
computeAuthToken({ { reinterpret_cast<const uint8_t*>(&i), sizeof(i) } },
|
||||
SHA_KEY,
|
||||
AES_256_KEY_LENGTH,
|
||||
&digest[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
AUTH_TOKEN_HMAC_SHA_SIZE);
|
||||
simEncryptKeyStore[i] = std::make_unique<SimEncryptKeyCtx>(i, reinterpret_cast<const char*>(&digest[0]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_G_H)
|
||||
#define FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_G_H
|
||||
#include "fdbserver/IPageEncryptionKeyProvider.actor.g.h"
|
||||
#elif !defined(FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_H)
|
||||
#define FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_H
|
||||
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
|
||||
|
@ -208,14 +208,15 @@ private:
|
|||
Reference<BlobCipherKey> generateCipherKey(const BlobCipherDetails& cipherDetails) {
|
||||
static unsigned char SHA_KEY[] = "3ab9570b44b8315fdb261da6b1b6c13b";
|
||||
Arena arena;
|
||||
uint8_t digest[AUTH_TOKEN_SIZE];
|
||||
computeAuthToken(reinterpret_cast<const unsigned char*>(&cipherDetails.baseCipherId),
|
||||
sizeof(EncryptCipherBaseKeyId),
|
||||
SHA_KEY,
|
||||
AES_256_KEY_LENGTH,
|
||||
&digest[0],
|
||||
AUTH_TOKEN_SIZE);
|
||||
ASSERT_EQ(AUTH_TOKEN_SIZE, AES_256_KEY_LENGTH);
|
||||
uint8_t digest[AUTH_TOKEN_HMAC_SHA_SIZE];
|
||||
computeAuthToken(
|
||||
{ { reinterpret_cast<const uint8_t*>(&cipherDetails.baseCipherId), sizeof(EncryptCipherBaseKeyId) } },
|
||||
SHA_KEY,
|
||||
AES_256_KEY_LENGTH,
|
||||
&digest[0],
|
||||
EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA,
|
||||
AUTH_TOKEN_HMAC_SHA_SIZE);
|
||||
ASSERT_EQ(AUTH_TOKEN_HMAC_SHA_SIZE, AES_256_KEY_LENGTH);
|
||||
return makeReference<BlobCipherKey>(cipherDetails.encryptDomainId,
|
||||
cipherDetails.baseCipherId,
|
||||
&digest[0],
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#ifndef FDBSERVER_IPAGER_H
|
||||
#define FDBSERVER_IPAGER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdint.h>
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
|
@ -28,8 +29,10 @@
|
|||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
#include "fdbserver/IClosable.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastAlloc.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/ProtocolVersion.h"
|
||||
|
||||
|
@ -369,7 +372,7 @@ public:
|
|||
Header* h = reinterpret_cast<Header*>(header);
|
||||
EncryptBlobCipherAes265Ctr cipher(cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
getEncryptAuthTokenMode(ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE),
|
||||
BlobCipherMetrics::KV_REDWOOD);
|
||||
Arena arena;
|
||||
StringRef ciphertext = cipher.encrypt(payload, len, h, arena)->toStringRef();
|
||||
|
|
|
@ -57,7 +57,7 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
enableTest = true;
|
||||
minDomainId = 1000 + (++seed * 30) + 1;
|
||||
maxDomainId = deterministicRandom()->randomInt(minDomainId, minDomainId + 50) + 5;
|
||||
TraceEvent("EKPTest_Init").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
TraceEvent("EKPTestInit").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
}
|
||||
}
|
||||
|
||||
TraceEvent("SimEmptyDomainIdCache_Done").log();
|
||||
TraceEvent("SimEmptyDomainIdCacheDone").log();
|
||||
return Void();
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
state int expectedHits;
|
||||
state int expectedMisses;
|
||||
|
||||
TraceEvent("SimPartialDomainIdCache_Start").log();
|
||||
TraceEvent("SimPartialDomainIdCacheStart").log();
|
||||
|
||||
self->domainInfos.clear();
|
||||
|
||||
|
@ -178,14 +178,14 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
}
|
||||
self->domainInfos.clear();
|
||||
|
||||
TraceEvent("SimPartialDomainIdCache_Done").log();
|
||||
TraceEvent("SimPartialDomainIdCacheDone").log();
|
||||
return Void();
|
||||
}
|
||||
|
||||
ACTOR Future<Void> simRandomBaseCipherIdCache(EncryptKeyProxyTestWorkload* self) {
|
||||
state int expectedHits;
|
||||
|
||||
TraceEvent("SimRandomDomainIdCache_Start").log();
|
||||
TraceEvent("SimRandomDomainIdCacheStart").log();
|
||||
|
||||
self->domainInfos.clear();
|
||||
for (int i = 0; i < self->numDomains; i++) {
|
||||
|
@ -271,14 +271,14 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
}
|
||||
}
|
||||
|
||||
TraceEvent("SimRandomDomainIdCache_Done").log();
|
||||
TraceEvent("SimRandomDomainIdCacheDone").log();
|
||||
return Void();
|
||||
}
|
||||
|
||||
ACTOR Future<Void> simLookupInvalidKeyId(EncryptKeyProxyTestWorkload* self) {
|
||||
Arena arena;
|
||||
|
||||
TraceEvent("SimLookupInvalidKeyId_Start").log();
|
||||
TraceEvent("SimLookupInvalidKeyIdStart").log();
|
||||
|
||||
// Prepare a lookup with valid and invalid keyIds - SimEncryptKmsProxy should throw encrypt_key_not_found()
|
||||
EKPGetBaseCipherKeysByIdsRequest req;
|
||||
|
@ -294,7 +294,7 @@ struct EncryptKeyProxyTestWorkload : TestWorkload {
|
|||
ASSERT(rep.error.present());
|
||||
ASSERT_EQ(rep.error.get().code(), error_code_encrypt_key_not_found);
|
||||
|
||||
TraceEvent("SimLookupInvalidKeyId_Done").log();
|
||||
TraceEvent("SimLookupInvalidKeyIdDone").log();
|
||||
return Void();
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
.detail("EnableTTL", enableTTLTest);
|
||||
}
|
||||
|
||||
~EncryptionOpsWorkload() { TraceEvent("EncryptionOpsWorkload.Done").log(); }
|
||||
~EncryptionOpsWorkload() { TraceEvent("EncryptionOpsWorkloadDone").log(); }
|
||||
|
||||
bool isFixedSizePayload() { return mode == 1; }
|
||||
|
||||
|
@ -174,7 +174,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
void setupCipherEssentials() {
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
|
||||
TraceEvent("SetupCipherEssentials.Start").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
TraceEvent("SetupCipherEssentialsStart").detail("MinDomainId", minDomainId).detail("MaxDomainId", maxDomainId);
|
||||
|
||||
uint8_t buff[AES_256_KEY_LENGTH];
|
||||
std::vector<Reference<BlobCipherKey>> cipherKeys;
|
||||
|
@ -208,7 +208,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
ASSERT_EQ(memcmp(latestCipher->rawBaseCipher(), buff, cipherLen), 0);
|
||||
headerRandomSalt = latestCipher->getSalt();
|
||||
|
||||
TraceEvent("SetupCipherEssentials.Done")
|
||||
TraceEvent("SetupCipherEssentialsDone")
|
||||
.detail("MinDomainId", minDomainId)
|
||||
.detail("MaxDomainId", maxDomainId)
|
||||
.detail("HeaderBaseCipherId", headerBaseCipherId)
|
||||
|
@ -216,6 +216,8 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
}
|
||||
|
||||
void resetCipherEssentials() {
|
||||
TraceEvent("ResetCipherEssentialsStart").detail("Min", minDomainId).detail("Max", maxDomainId);
|
||||
|
||||
Reference<BlobCipherKeyCache> cipherKeyCache = BlobCipherKeyCache::getInstance();
|
||||
for (EncryptCipherDomainId id = minDomainId; id <= maxDomainId; id++) {
|
||||
cipherKeyCache->resetEncryptDomainId(id);
|
||||
|
@ -225,7 +227,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
cipherKeyCache->resetEncryptDomainId(SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID);
|
||||
cipherKeyCache->resetEncryptDomainId(ENCRYPT_HEADER_DOMAIN_ID);
|
||||
|
||||
TraceEvent("ResetCipherEssentials.Done").log();
|
||||
TraceEvent("ResetCipherEssentialsDone");
|
||||
}
|
||||
|
||||
void updateLatestBaseCipher(const EncryptCipherDomainId encryptDomainId,
|
||||
|
@ -272,11 +274,12 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
uint8_t* payload,
|
||||
int len,
|
||||
const EncryptAuthTokenMode authMode,
|
||||
const EncryptAuthTokenAlgo authAlgo,
|
||||
BlobCipherEncryptHeader* header) {
|
||||
uint8_t iv[AES_256_IV_LENGTH];
|
||||
deterministicRandom()->randomBytes(&iv[0], AES_256_IV_LENGTH);
|
||||
EncryptBlobCipherAes265Ctr encryptor(
|
||||
textCipherKey, headerCipherKey, &iv[0], AES_256_IV_LENGTH, authMode, BlobCipherMetrics::TEST);
|
||||
textCipherKey, headerCipherKey, &iv[0], AES_256_IV_LENGTH, authMode, authAlgo, BlobCipherMetrics::TEST);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Reference<EncryptBuf> encrypted = encryptor.encrypt(payload, len, header, arena);
|
||||
|
@ -307,6 +310,8 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
header.cipherHeaderDetails.salt);
|
||||
ASSERT(cipherKey.isValid());
|
||||
ASSERT(cipherKey->isEqual(orgCipherKey));
|
||||
ASSERT(headerCipherKey.isValid() ||
|
||||
header.flags.authTokenMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE);
|
||||
|
||||
DecryptBlobCipherAes256Ctr decryptor(cipherKey, headerCipherKey, header.iv, BlobCipherMetrics::TEST);
|
||||
const bool validateHeaderAuthToken = deterministicRandom()->randomInt(0, 100) < 65;
|
||||
|
@ -374,12 +379,14 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
|
||||
// Encrypt the payload - generates BlobCipherEncryptHeader to assist decryption later
|
||||
BlobCipherEncryptHeader header;
|
||||
const EncryptAuthTokenMode authMode = deterministicRandom()->randomInt(0, 100) < 50
|
||||
? ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE
|
||||
: ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI;
|
||||
const EncryptAuthTokenMode authMode = getRandomAuthTokenMode();
|
||||
const EncryptAuthTokenAlgo authAlgo = authMode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE
|
||||
? EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE
|
||||
: getRandomAuthTokenAlgo();
|
||||
|
||||
try {
|
||||
Reference<EncryptBuf> encrypted =
|
||||
doEncryption(cipherKey, headerCipherKey, buff.get(), dataLen, authMode, &header);
|
||||
doEncryption(cipherKey, headerCipherKey, buff.get(), dataLen, authMode, authAlgo, &header);
|
||||
|
||||
// Decrypt the payload - parses the BlobCipherEncryptHeader, fetch corresponding cipherKey and
|
||||
// decrypt
|
||||
|
@ -388,7 +395,8 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
TraceEvent("Failed")
|
||||
.detail("DomainId", encryptDomainId)
|
||||
.detail("BaseCipherId", cipherKey->getBaseCipherId())
|
||||
.detail("AuthMode", authMode);
|
||||
.detail("AuthTokenMode", authMode)
|
||||
.detail("AuthTokenAlgo", authAlgo);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
@ -425,7 +433,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
state int64_t refreshAt;
|
||||
state int64_t expAt;
|
||||
|
||||
TraceEvent("TestBlobCipherCacheTTL.Start").detail("DomId", domId);
|
||||
TraceEvent("TestBlobCipherCacheTTLStart").detail("DomId", domId);
|
||||
|
||||
deterministicRandom()->randomBytes(baseCipher.get(), AES_256_KEY_LENGTH);
|
||||
|
||||
|
@ -436,7 +444,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
cipherKey = cipherKeyCache->getLatestCipherKey(domId);
|
||||
compareCipherDetails(cipherKey, domId, baseCipherId, baseCipher.get(), AES_256_KEY_LENGTH, refreshAt, expAt);
|
||||
|
||||
TraceEvent("TestBlobCipherCacheTTL.NonRevocableNoExpiry").detail("DomId", domId);
|
||||
TraceEvent("TestBlobCipherCacheTTLNonRevocableNoExpiry").detail("DomId", domId);
|
||||
|
||||
// Validate 'non-revocable' cipher with expiration
|
||||
state EncryptCipherBaseKeyId baseCipherId_1 = baseCipherId + 1;
|
||||
|
@ -454,7 +462,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
ASSERT(cipherKey.isValid());
|
||||
compareCipherDetails(cipherKey, domId, baseCipherId_1, baseCipher.get(), AES_256_KEY_LENGTH, refreshAt, expAt);
|
||||
|
||||
TraceEvent("TestBlobCipherCacheTTL.NonRevocableWithExpiry").detail("DomId", domId);
|
||||
TraceEvent("TestBlobCipherCacheTTLNonRevocableWithExpiry").detail("DomId", domId);
|
||||
|
||||
// Validate 'revocable' cipher with expiration
|
||||
state EncryptCipherBaseKeyId baseCipherId_2 = baseCipherId + 2;
|
||||
|
@ -479,7 +487,7 @@ struct EncryptionOpsWorkload : TestWorkload {
|
|||
cipherKey = cipherKeyCache->getCipherKey(domId, baseCipherId_2, salt);
|
||||
ASSERT(!cipherKey.isValid());
|
||||
|
||||
TraceEvent("TestBlobCipherCacheTTL.End").detail("DomId", domId);
|
||||
TraceEvent("TestBlobCipherCacheTTLEnd").detail("DomId", domId);
|
||||
return Void();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/Trace.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
@ -71,3 +73,72 @@ std::string getEncryptDbgTraceKeyWithTS(std::string_view prefix,
|
|||
boost::format fmter("%s.%lld.%s.%llu.%lld.%lld");
|
||||
return boost::str(boost::format(fmter % prefix % domainId % dName % baseCipherId % refAfterTS % expAfterTS));
|
||||
}
|
||||
|
||||
int getEncryptHeaderAuthTokenSize(int algo) {
|
||||
switch (algo) {
|
||||
case ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA:
|
||||
return 32;
|
||||
case ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC:
|
||||
return 16;
|
||||
default:
|
||||
throw not_implemented();
|
||||
}
|
||||
}
|
||||
|
||||
bool isEncryptHeaderAuthTokenAlgoValid(const EncryptAuthTokenAlgo algo) {
|
||||
return algo >= EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE &&
|
||||
algo < EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_LAST;
|
||||
}
|
||||
|
||||
bool isEncryptHeaderAuthTokenModeValid(const EncryptAuthTokenMode mode) {
|
||||
return mode >= EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
|
||||
mode < EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_LAST;
|
||||
}
|
||||
|
||||
bool isEncryptHeaderAuthTokenDetailsValid(const EncryptAuthTokenMode mode, const EncryptAuthTokenAlgo algo) {
|
||||
if (!isEncryptHeaderAuthTokenModeValid(mode) || !isEncryptHeaderAuthTokenAlgoValid(algo) ||
|
||||
(mode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
|
||||
algo != EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE) ||
|
||||
(mode != EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
|
||||
algo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routine enables mapping EncryptHeader authTokenAlgo for a given authTokenMode; rules followed are:
|
||||
// 1. AUTH_TOKEN_NONE overrides authTokenAlgo configuration (as expected)
|
||||
// 2. AuthToken mode governed by the FLOW_KNOBS->ENCRYPT_HEADER_AUTH_TOKEN_ALGO
|
||||
EncryptAuthTokenAlgo getAuthTokenAlgoFromMode(const EncryptAuthTokenMode mode) {
|
||||
EncryptAuthTokenAlgo algo;
|
||||
|
||||
if (mode == EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) {
|
||||
// TOKEN_MODE_NONE overrides authTokenAlgo
|
||||
algo = EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE;
|
||||
} else {
|
||||
algo = (EncryptAuthTokenAlgo)FLOW_KNOBS->ENCRYPT_HEADER_AUTH_TOKEN_ALGO;
|
||||
// Ensure cluster authTokenAlgo sanity
|
||||
if (algo == EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE) {
|
||||
TraceEvent(SevWarn, "AuthTokenAlgoMisconfiguration").detail("Algo", algo).detail("Mode", mode);
|
||||
throw not_implemented();
|
||||
}
|
||||
}
|
||||
ASSERT(isEncryptHeaderAuthTokenDetailsValid(mode, algo));
|
||||
return algo;
|
||||
}
|
||||
|
||||
EncryptAuthTokenMode getRandomAuthTokenMode() {
|
||||
std::vector<EncryptAuthTokenMode> modes = { EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_MULTI };
|
||||
int idx = deterministicRandom()->randomInt(0, modes.size());
|
||||
return modes[idx];
|
||||
}
|
||||
|
||||
EncryptAuthTokenAlgo getRandomAuthTokenAlgo() {
|
||||
EncryptAuthTokenAlgo algo = deterministicRandom()->coinflip()
|
||||
? EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC
|
||||
: EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA;
|
||||
|
||||
return algo;
|
||||
}
|
|
@ -18,8 +18,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "flow/flow.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/Knobs.h"
|
||||
#include "flow/BooleanParam.h"
|
||||
#include "flow/UnitTest.h"
|
||||
|
@ -301,6 +302,10 @@ void FlowKnobs::initialize(Randomize randomize, IsSimulated isSimulated) {
|
|||
init( TOKEN_CACHE_SIZE, 100 );
|
||||
init( ENCRYPT_KEY_CACHE_LOGGING_INTERVAL, 5.0 );
|
||||
init( ENCRYPT_KEY_CACHE_LOGGING_SAMPLE_SIZE, 1000 );
|
||||
// Refer to EncryptUtil::EncryptAuthTokenAlgo for more details
|
||||
init( ENCRYPT_HEADER_AUTH_TOKEN_ENABLED, true ); if ( randomize && BUGGIFY ) { ENCRYPT_HEADER_AUTH_TOKEN_ENABLED = !ENCRYPT_HEADER_AUTH_TOKEN_ENABLED; }
|
||||
init( ENCRYPT_HEADER_AUTH_TOKEN_ALGO, 1 ); if ( randomize && BUGGIFY ) { ENCRYPT_HEADER_AUTH_TOKEN_ALGO = getRandomAuthTokenAlgo(); }
|
||||
|
||||
|
||||
// REST Client
|
||||
init( RESTCLIENT_MAX_CONNECTIONPOOL_SIZE, 10 );
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#define AUTH_TOKEN_SIZE 32
|
||||
constexpr const int AUTH_TOKEN_HMAC_SHA_SIZE = 32;
|
||||
constexpr const int AUTH_TOKEN_AES_CMAC_SIZE = 16;
|
||||
constexpr const int AUTH_TOKEN_MAX_SIZE = AUTH_TOKEN_HMAC_SHA_SIZE;
|
||||
|
||||
using EncryptCipherDomainId = int64_t;
|
||||
using EncryptCipherDomainNameRef = StringRef;
|
||||
|
@ -78,6 +80,23 @@ typedef enum {
|
|||
static_assert(EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_LAST <= std::numeric_limits<uint8_t>::max(),
|
||||
"EncryptHeaderAuthToken value overflow");
|
||||
|
||||
typedef enum {
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_ALGO_NONE = 0,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA = 1,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC = 2,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_ALGO_LAST = 3 // Always the last element
|
||||
} EncryptAuthTokenAlgo;
|
||||
|
||||
static_assert(EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_LAST <= std::numeric_limits<uint8_t>::max(),
|
||||
"EncryptHeaerAuthTokenAlgo value overflow");
|
||||
|
||||
bool isEncryptHeaderAuthTokenModeValid(const EncryptAuthTokenMode mode);
|
||||
bool isEncryptHeaderAuthTokenAlgoValid(const EncryptAuthTokenAlgo algo);
|
||||
bool isEncryptHeaderAuthTokenDetailsValid(const EncryptAuthTokenMode mode, const EncryptAuthTokenAlgo algo);
|
||||
EncryptAuthTokenAlgo getAuthTokenAlgoFromMode(const EncryptAuthTokenMode mode);
|
||||
EncryptAuthTokenMode getRandomAuthTokenMode();
|
||||
EncryptAuthTokenAlgo getRandomAuthTokenAlgo();
|
||||
|
||||
constexpr std::string_view ENCRYPT_DBG_TRACE_CACHED_PREFIX = "Chd";
|
||||
constexpr std::string_view ENCRYPT_DBG_TRACE_QUERY_PREFIX = "Qry";
|
||||
constexpr std::string_view ENCRYPT_DBG_TRACE_INSERT_PREFIX = "Ins";
|
||||
|
@ -96,4 +115,6 @@ std::string getEncryptDbgTraceKeyWithTS(std::string_view prefix,
|
|||
int64_t refAfterTS,
|
||||
int64_t expAfterTS);
|
||||
|
||||
int getEncryptHeaderAuthTokenSize(int algo);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -364,6 +364,8 @@ public:
|
|||
int64_t ENCRYPT_KEY_REFRESH_INTERVAL;
|
||||
double ENCRYPT_KEY_CACHE_LOGGING_INTERVAL;
|
||||
double ENCRYPT_KEY_CACHE_LOGGING_SAMPLE_SIZE;
|
||||
bool ENCRYPT_HEADER_AUTH_TOKEN_ENABLED;
|
||||
int ENCRYPT_HEADER_AUTH_TOKEN_ALGO;
|
||||
|
||||
// Authorization
|
||||
int TOKEN_CACHE_SIZE;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[configuration]
|
||||
buggify = false
|
||||
testClass = "Encryption"
|
||||
|
||||
[[knobs]]
|
||||
|
|
Loading…
Reference in New Issue