From 03f1d13be343f7adbab33e3f0337fb0d7269ffb4 Mon Sep 17 00:00:00 2001 From: Ata E Husain Bohra Date: Thu, 29 Sep 2022 16:18:55 -0700 Subject: [PATCH] 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 --- fdbclient/BlobCipher.cpp | 584 ++++++++++++++---- fdbclient/BlobGranuleFiles.cpp | 28 +- fdbclient/include/fdbclient/BlobCipher.h | 51 +- .../include/fdbclient/CommitTransaction.h | 15 +- fdbserver/EncryptKeyProxy.actor.cpp | 16 +- fdbserver/KeyValueStoreMemory.actor.cpp | 10 +- fdbserver/SimKmsConnector.actor.cpp | 8 +- .../IPageEncryptionKeyProvider.actor.h | 19 +- fdbserver/include/fdbserver/IPager.h | 5 +- .../workloads/EncryptKeyProxyTest.actor.cpp | 16 +- fdbserver/workloads/EncryptionOps.actor.cpp | 36 +- flow/EncryptUtils.cpp | 71 +++ flow/Knobs.cpp | 7 +- flow/include/flow/EncryptUtils.h | 23 +- flow/include/flow/Knobs.h | 2 + tests/fast/EncryptionOps.toml | 1 + 16 files changed, 703 insertions(+), 189 deletions(-) diff --git a/fdbclient/BlobCipher.cpp b/fdbclient/BlobCipher.cpp index 4fc803bcc6..09a8bbd5ee 100644 --- a/fdbclient/BlobCipher.cpp +++ b/fdbclient/BlobCipher.cpp @@ -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 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 tCipherKey, + Reference 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 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 tCipherKey, + Reference 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 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 = makeReference(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 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(header), - sizeof(BlobCipherEncryptHeader)); - computeAuthToken(ciphertext, - bytes + finalBytes + sizeof(BlobCipherEncryptHeader), + computeAuthToken({ { ciphertext, bytes + finalBytes }, + { reinterpret_cast(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(&header->cipherTextDetails.salt), sizeof(EncryptCipherRandomSalt), &header->multiAuthTokens.cipherTextAuthToken[0], - AUTH_TOKEN_SIZE); - computeAuthToken(reinterpret_cast(header), - sizeof(BlobCipherEncryptHeader), + EncryptAuthTokenAlgo::ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA, + AUTH_TOKEN_MAX_SIZE); + computeAuthToken({ { reinterpret_cast(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 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(&headerCopy), reinterpret_cast(&header), sizeof(BlobCipherEncryptHeader)); - memset(reinterpret_cast(&headerCopy.multiAuthTokens.headerAuthToken), 0, AUTH_TOKEN_SIZE); - uint8_t computedHeaderAuthToken[AUTH_TOKEN_SIZE]; - computeAuthToken(reinterpret_cast(&headerCopy), - sizeof(BlobCipherEncryptHeader), + memset(reinterpret_cast(&headerCopy.multiAuthTokens.headerAuthToken), 0, AUTH_TOKEN_MAX_SIZE); + uint8_t computedHeaderAuthToken[AUTH_TOKEN_MAX_SIZE]{}; + computeAuthToken({ { reinterpret_cast(&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(&header), sizeof(BlobCipherEncryptHeader)); // ensure the 'authToken' is reset before computing the 'authentication token' - BlobCipherEncryptHeader* eHeader = (BlobCipherEncryptHeader*)(&buff[ciphertextLen]); - memset(reinterpret_cast(&eHeader->singleAuthToken), 0, 2 * AUTH_TOKEN_SIZE); - - uint8_t computed[AUTH_TOKEN_SIZE]; - computeAuthToken(buff, - ciphertextLen + sizeof(BlobCipherEncryptHeader), + BlobCipherEncryptHeader headerCopy; + memcpy(reinterpret_cast(&headerCopy), + reinterpret_cast(&header), + sizeof(BlobCipherEncryptHeader)); + memset(reinterpret_cast(&headerCopy.singleAuthToken), 0, 2 * AUTH_TOKEN_MAX_SIZE); + uint8_t computed[AUTH_TOKEN_MAX_SIZE]; + computeAuthToken({ { ciphertext, ciphertextLen }, + { reinterpret_cast(&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(&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 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 decrypted = makeReference(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 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>& 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>& 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>& 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(), + headerCipherKey, iv, AES_256_IV_LENGTH, - ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE, + EncryptAuthTokenMode::ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE, BlobCipherMetrics::TEST); BlobCipherEncryptHeader header; Reference 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(&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 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 tCipherKeyKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId, + header.cipherTextDetails.baseCipherId, + header.cipherTextDetails.salt); + Reference 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 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(&headerCopy), + reinterpret_cast(&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(&headerCopy), + reinterpret_cast(&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(&headerCopy), + reinterpret_cast(&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 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 tCipherKeyKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId, header.cipherTextDetails.baseCipherId, @@ -1242,7 +1474,7 @@ TEST_CASE("flow/BlobCipher") { memcpy(reinterpret_cast(&headerCopy), reinterpret_cast(&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 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 tCipherKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId, header.cipherTextDetails.baseCipherId, @@ -1352,7 +1589,7 @@ TEST_CASE("flow/BlobCipher") { memcpy(reinterpret_cast(&headerCopy), reinterpret_cast(&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(&headerCopy), reinterpret_cast(&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 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 tCipherKey = cipherKeyCache->getCipherKey(header.cipherTextDetails.encryptDomainId, + header.cipherTextDetails.baseCipherId, + header.cipherTextDetails.salt); + Reference 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 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(&headerCopy), + reinterpret_cast(&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(&headerCopy), + reinterpret_cast(&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(&headerCopy), + reinterpret_cast(&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(&headerCopy), + reinterpret_cast(&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(); } diff --git a/fdbclient/BlobGranuleFiles.cpp b/fdbclient/BlobGranuleFiles.cpp index 04edc8e90c..8553296195 100644 --- a/fdbclient/BlobGranuleFiles.cpp +++ b/fdbclient/BlobGranuleFiles.cpp @@ -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(); diff --git a/fdbclient/include/fdbclient/BlobCipher.h b/fdbclient/include/fdbclient/BlobCipher.h index 52e800c250..be2eae72b8 100644 --- a/fdbclient/include/fdbclient/BlobCipher.h +++ b/fdbclient/include/fdbclient/BlobCipher.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -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 tCipherKey, Reference hCipherKey, + const uint8_t* iv, + const int ivLen, const EncryptAuthTokenMode mode, + const EncryptAuthTokenAlgo algo, + BlobCipherMetrics::UsageType usageType); + EncryptBlobCipherAes265Ctr(Reference tCipherKey, + Reference hCipherKey, + const EncryptAuthTokenMode mode, + BlobCipherMetrics::UsageType usageType); + EncryptBlobCipherAes265Ctr(Reference tCipherKey, + Reference 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>& 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>& payload, uint8_t* digest, int digestlen); + +private: + CMAC_CTX* ctx; +}; + +void computeAuthToken(const std::vector>& 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 \ No newline at end of file diff --git a/fdbclient/include/fdbclient/CommitTransaction.h b/fdbclient/include/fdbclient/CommitTransaction.h index f45280369d..7bc080b130 100644 --- a/fdbclient/include/fdbclient/CommitTransaction.h +++ b/fdbclient/include/fdbclient/CommitTransaction.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(header), sizeof(BlobCipherEncryptHeader)); StringRef payload = diff --git a/fdbserver/EncryptKeyProxy.actor.cpp b/fdbserver/EncryptKeyProxy.actor.cpp index 55f3d83d4e..c1ed8bf2e2 100644 --- a/fdbserver/EncryptKeyProxy.actor.cpp +++ b/fdbserver/EncryptKeyProxy.actor.cpp @@ -411,7 +411,7 @@ ACTOR Future getCipherKeysByBaseCipherKeyIds(ReferencemyId) + TraceEvent(SevError, "GetCipherKeysByKeyIdsMappingNotFound", ekpProxyData->myId) .detail("DomainId", item.encryptDomainId); throw encrypt_keys_fetch_failed(); } @@ -546,7 +546,7 @@ ACTOR Future getLatestCipherKeys(Reference 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 refreshEncryptionKeysCore(Reference 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 refreshEncryptionKeysCore(Reference 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 refreshEncryptionKeysCore(Reference 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 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 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 encryptKeyProxyServer(EncryptKeyProxyInterface ekpInterface, } } } catch (Error& e) { - TraceEvent("EKP_Terminated", self->myId).errorUnsuppressed(e); + TraceEvent("EKPTerminated", self->myId).errorUnsuppressed(e); } return Void(); diff --git a/fdbserver/KeyValueStoreMemory.actor.cpp b/fdbserver/KeyValueStoreMemory.actor.cpp index 980c3df122..3e6ecb8b3f 100644 --- a/fdbserver/KeyValueStoreMemory.actor.cpp +++ b/fdbserver/KeyValueStoreMemory.actor.cpp @@ -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 = diff --git a/fdbserver/SimKmsConnector.actor.cpp b/fdbserver/SimKmsConnector.actor.cpp index 1c741e3185..da903870bd 100644 --- a/fdbserver/SimKmsConnector.actor.cpp +++ b/fdbserver/SimKmsConnector.actor.cpp @@ -66,13 +66,13 @@ struct SimKmsConnectorContext : NonCopyable, ReferenceCounted(&i), - sizeof(i), + uint8_t digest[AUTH_TOKEN_HMAC_SHA_SIZE]; + computeAuthToken({ { reinterpret_cast(&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(i, reinterpret_cast(&digest[0])); } } diff --git a/fdbserver/include/fdbserver/IPageEncryptionKeyProvider.actor.h b/fdbserver/include/fdbserver/IPageEncryptionKeyProvider.actor.h index 40dfe0b48e..a5268c001c 100644 --- a/fdbserver/include/fdbserver/IPageEncryptionKeyProvider.actor.h +++ b/fdbserver/include/fdbserver/IPageEncryptionKeyProvider.actor.h @@ -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 generateCipherKey(const BlobCipherDetails& cipherDetails) { static unsigned char SHA_KEY[] = "3ab9570b44b8315fdb261da6b1b6c13b"; Arena arena; - uint8_t digest[AUTH_TOKEN_SIZE]; - computeAuthToken(reinterpret_cast(&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(&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(cipherDetails.encryptDomainId, cipherDetails.baseCipherId, &digest[0], diff --git a/fdbserver/include/fdbserver/IPager.h b/fdbserver/include/fdbserver/IPager.h index f3aa119afc..550ded88ce 100644 --- a/fdbserver/include/fdbserver/IPager.h +++ b/fdbserver/include/fdbserver/IPager.h @@ -21,6 +21,7 @@ #ifndef FDBSERVER_IPAGER_H #define FDBSERVER_IPAGER_H + #include #include #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); 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(); diff --git a/fdbserver/workloads/EncryptKeyProxyTest.actor.cpp b/fdbserver/workloads/EncryptKeyProxyTest.actor.cpp index e9fb3caccc..7b00e15b6d 100644 --- a/fdbserver/workloads/EncryptKeyProxyTest.actor.cpp +++ b/fdbserver/workloads/EncryptKeyProxyTest.actor.cpp @@ -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 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 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(); } diff --git a/fdbserver/workloads/EncryptionOps.actor.cpp b/fdbserver/workloads/EncryptionOps.actor.cpp index 285af5ecd6..623e7c4433 100644 --- a/fdbserver/workloads/EncryptionOps.actor.cpp +++ b/fdbserver/workloads/EncryptionOps.actor.cpp @@ -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 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> 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 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 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 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(); } diff --git a/flow/EncryptUtils.cpp b/flow/EncryptUtils.cpp index ee79aee5c4..67015293b0 100644 --- a/flow/EncryptUtils.cpp +++ b/flow/EncryptUtils.cpp @@ -19,6 +19,8 @@ */ #include "flow/EncryptUtils.h" +#include "flow/IRandom.h" +#include "flow/Knobs.h" #include "flow/Trace.h" #include @@ -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 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; +} \ No newline at end of file diff --git a/flow/Knobs.cpp b/flow/Knobs.cpp index f6734b44b7..ee65169c73 100644 --- a/flow/Knobs.cpp +++ b/flow/Knobs.cpp @@ -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 ); diff --git a/flow/include/flow/EncryptUtils.h b/flow/include/flow/EncryptUtils.h index 800817d410..ec5b00b231 100644 --- a/flow/include/flow/EncryptUtils.h +++ b/flow/include/flow/EncryptUtils.h @@ -29,7 +29,9 @@ #include #include -#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::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::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 diff --git a/flow/include/flow/Knobs.h b/flow/include/flow/Knobs.h index 8f82f3f4fa..11808c48e6 100644 --- a/flow/include/flow/Knobs.h +++ b/flow/include/flow/Knobs.h @@ -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; diff --git a/tests/fast/EncryptionOps.toml b/tests/fast/EncryptionOps.toml index 9aaa09f177..5002e5adfc 100644 --- a/tests/fast/EncryptionOps.toml +++ b/tests/fast/EncryptionOps.toml @@ -1,4 +1,5 @@ [configuration] +buggify = false testClass = "Encryption" [[knobs]]