diff --git a/fdbclient/BlobCipher.cpp b/fdbclient/BlobCipher.cpp index 23588a4c23..bea45205f4 100644 --- a/fdbclient/BlobCipher.cpp +++ b/fdbclient/BlobCipher.cpp @@ -60,6 +60,58 @@ #define BLOB_CIPHER_DEBUG false #define BLOB_CIPHER_SERIALIZATION_CHECKS false +namespace { +void validateEncryptHeaderFlagVersion(const int flagsVersion) { + ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); + + if (flagsVersion > CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) { + TraceEvent("EncryptHeaderUnsupportedFlagVersion") + .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) + .detail("Version", flagsVersion); + throw not_implemented(); + } +} + +void validateEncryptHeaderAlgoHeaderVersion(const EncryptCipherMode cipherMode, + const EncryptAuthTokenMode authMode, + const EncryptAuthTokenAlgo authAlgo, + const int version) { + ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); + + if (cipherMode != ENCRYPT_CIPHER_MODE_AES_256_CTR) { + TraceEvent("EncryptHeaderUnsupportedEncryptCipherMode") + .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) + .detail("CipherMode", cipherMode); + throw not_implemented(); + } + + int maxSupportedVersion = -1; + if (authMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) { + maxSupportedVersion = CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION; + } else { + ASSERT_EQ(authMode, ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE); + + if (authAlgo == ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA) { + maxSupportedVersion = CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_HMAC_SHA_AUTH_VERSION; + } else if (authAlgo == ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC) { + maxSupportedVersion = CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_AES_CMAC_AUTH_VERSION; + } else { + // Unknown encryption authentication algo + } + } + + if (version > maxSupportedVersion || maxSupportedVersion == -1) { + TraceEvent("EncryptHeaderUnsupportedEncryptAuthToken") + .detail("CipherMode", cipherMode) + .detail("AuthMode", authMode) + .detail("AuthAlgo", authAlgo) + .detail("AlgoHeaderVersion", version) + .detail("MaxSsupportedVersion", maxSupportedVersion); + throw not_implemented(); + } +} +} // namespace + // BlobCipherEncryptHeaderRef uint32_t BlobCipherEncryptHeaderRef::getHeaderSize(const int flagVersion, @@ -91,62 +143,93 @@ uint32_t BlobCipherEncryptHeaderRef::getHeaderSize(const int flagVersion, return total; } +const uint8_t* BlobCipherEncryptHeaderRef::getIV() const { + ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); + + validateEncryptHeaderFlagVersion(flagsVersion); + ASSERT_EQ(flagsVersion, 1); + + BlobCipherEncryptHeaderFlagsV1 flags = std::get(this->flags); + + validateEncryptHeaderAlgoHeaderVersion((EncryptCipherMode)flags.encryptMode, + (EncryptAuthTokenMode)flags.authTokenMode, + (EncryptAuthTokenAlgo)flags.authTokenAlgo, + algoHeaderVersion); + ASSERT_EQ(algoHeaderVersion, 1); + + return std::visit([](auto& h) { return h.iv; }, algoHeader); +} + +template +inline constexpr bool always_false_v = false; + +const EncryptHeaderCipherDetails BlobCipherEncryptHeaderRef::getCipherDetails() const { + ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); + + validateEncryptHeaderFlagVersion(flagsVersion); + ASSERT_EQ(flagsVersion, 1); + + BlobCipherEncryptHeaderFlagsV1 flags = std::get(this->flags); + + validateEncryptHeaderAlgoHeaderVersion((EncryptCipherMode)flags.encryptMode, + (EncryptAuthTokenMode)flags.authTokenMode, + (EncryptAuthTokenAlgo)flags.authTokenAlgo, + algoHeaderVersion); + ASSERT_EQ(algoHeaderVersion, 1); + + // TODO: Replace with "Overload visitor pattern" someday. + return std::visit( + [](auto&& h) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return EncryptHeaderCipherDetails(h.cipherTextDetails); + } else if constexpr (std::is_same_v> || + std::is_same_v>) { + return EncryptHeaderCipherDetails(h.cipherTextDetails, h.cipherHeaderDetails); + } else { + static_assert(always_false_v, "Unknown encryption authentication"); + } + }, + algoHeader); +} + void BlobCipherEncryptHeaderRef::validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails, const BlobCipherDetails& headerCipherDetails, const StringRef& ivRef) const { ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); - if (flagsVersion > CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) { - TraceEvent("ValidateEncryptHeaderUnsupportedFlagVersion") - .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) - .detail("Version", flagsVersion); - throw not_implemented(); - } + validateEncryptHeaderFlagVersion(flagsVersion); + ASSERT_EQ(flagsVersion, 1); BlobCipherEncryptHeaderFlagsV1 flags = std::get(this->flags); + + validateEncryptHeaderAlgoHeaderVersion((EncryptCipherMode)flags.encryptMode, + (EncryptAuthTokenMode)flags.authTokenMode, + (EncryptAuthTokenAlgo)flags.authTokenAlgo, + algoHeaderVersion); + ASSERT_EQ(algoHeaderVersion, 1); + BlobCipherDetails persistedTextCipherDetails; BlobCipherDetails persistedHeaderCipherDetails; uint8_t* persistedIV = nullptr; - if (flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) { - if (algoHeaderVersion > CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) { - TraceEvent("ValidateEncryptHeaderUnsupportedAlgoHeaderVersion") - .detail("AuthMode", "No-Auth") - .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) - .detail("Version", algoHeaderVersion); - throw not_implemented(); - } - persistedTextCipherDetails = std::get(this->algoHeader).cipherTextDetails; - persistedIV = (uint8_t*)(&std::get(this->algoHeader).iv[0]); - } else { - if (flags.authTokenAlgo == ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA) { - if (algoHeaderVersion > CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) { - TraceEvent("ValidateEncryptHeaderUnsupportedAlgoHeaderVersion") - .detail("AuthMode", "Hmac-Sha") - .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_HMAC_SHA_AUTH_VERSION) - .detail("Version", algoHeaderVersion); - } - persistedTextCipherDetails = - std::get>(this->algoHeader).cipherTextDetails; - persistedHeaderCipherDetails = - std::get>(this->algoHeader).cipherHeaderDetails; - persistedIV = (uint8_t*)(&std::get>(this->algoHeader).iv[0]); - } else if (flags.authTokenAlgo == ENCRYPT_HEADER_AUTH_TOKEN_ALGO_AES_CMAC) { - if (algoHeaderVersion > CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_AES_CMAC_AUTH_VERSION) { - TraceEvent("ValidateEncryptHeaderUnsupportedAlgoHeaderVersion") - .detail("AuthMode", "Aes-Cmac") - .detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_AES_CMAC_AUTH_VERSION) - .detail("Version", algoHeaderVersion); - } - persistedTextCipherDetails = - std::get>(this->algoHeader).cipherTextDetails; - persistedHeaderCipherDetails = - std::get>(this->algoHeader).cipherHeaderDetails; - persistedIV = (uint8_t*)(&std::get>(this->algoHeader).iv[0]); - } else { - throw not_implemented(); - } - } + // TODO: Replace with "Overload visitor pattern" someday. + return std::visit( + [&persistedTextCipherDetails, &persistedHeaderCipherDetails, &persistedIV](auto&& h) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + persistedTextCipherDetails = h.cipherTextDetails; + persistedIV = (uint8_t*)&h.iv[0]; + } else if constexpr (std::is_same_v> || + std::is_same_v>) { + persistedTextCipherDetails = h.cipherTextDetails; + persistedHeaderCipherDetails = h.cipherHeaderDetails; + persistedIV = (uint8_t*)&h.iv[0]; + } else { + static_assert(always_false_v, "Unknown encryption authentication"); + } + }, + algoHeader); // Validate encryption header 'cipherHeader' details sanity if (flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && @@ -1808,8 +1891,17 @@ void testConfigurableEncryptionHeaderNoAuthMode(const int minDomainId) { BlobCipherEncryptHeaderFlagsV1 flags = std::get(headerRef.flags); AesCtrNoAuthV1 noAuth = std::get(headerRef.algoHeader); - Standalone serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef); + const uint8_t* headerIV = headerRef.getIV(); + ASSERT_EQ(memcmp(&headerIV[0], &iv[0], AES_256_IV_LENGTH), 0); + + EncryptHeaderCipherDetails validateDetails = headerRef.getCipherDetails(); + ASSERT(validateDetails.textCipherDetails.isValid() && + validateDetails.textCipherDetails == + BlobCipherDetails(cipherKey->getDomainId(), cipherKey->getBaseCipherId(), cipherKey->getSalt())); + ASSERT(!validateDetails.headerCipherDetails.present()); + + Standalone serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef); BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef); BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get(validateHeader.flags); ASSERT(validateFlags == flags); @@ -2093,8 +2185,20 @@ void testConfigurableEncryptionHeaderSingleAuthMode(int minDomainId) { BlobCipherEncryptHeaderFlagsV1 flags = std::get(headerRef.flags); AesCtrWithAuthV1 algoHeader = std::get>(headerRef.algoHeader); - Standalone serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef); + const uint8_t* headerIV = headerRef.getIV(); + ASSERT_EQ(memcmp(&headerIV[0], &iv[0], AES_256_IV_LENGTH), 0); + + EncryptHeaderCipherDetails validateDetails = headerRef.getCipherDetails(); + ASSERT(validateDetails.textCipherDetails.isValid() && + validateDetails.textCipherDetails == + BlobCipherDetails(cipherKey->getDomainId(), cipherKey->getBaseCipherId(), cipherKey->getSalt())); + ASSERT(validateDetails.headerCipherDetails.present() && validateDetails.headerCipherDetails.get().isValid() && + validateDetails.headerCipherDetails.get() == BlobCipherDetails(headerCipherKey->getDomainId(), + headerCipherKey->getBaseCipherId(), + headerCipherKey->getSalt())); + + Standalone serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef); BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef); BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get(validateHeader.flags); ASSERT(validateFlags == flags); @@ -2105,7 +2209,7 @@ void testConfigurableEncryptionHeaderSingleAuthMode(int minDomainId) { ASSERT_EQ(memcmp(&iv[0], &validateAlgo.iv[0], AES_256_IV_LENGTH), 0); ASSERT_EQ(memcmp(&algoHeader.authToken[0], &validateAlgo.authToken[0], AuthTokenSize), 0); - TraceEvent("HmacShaHeaderSize") + TraceEvent("HeaderSize") .detail("Flags", sizeof(flags)) .detail("AlgoHeader", sizeof(algoHeader)) .detail("TotalHeader", serHeaderRef.size()); diff --git a/fdbclient/include/fdbclient/BlobCipher.h b/fdbclient/include/fdbclient/BlobCipher.h index 89a16f58e8..b1b8c231d4 100644 --- a/fdbclient/include/fdbclient/BlobCipher.h +++ b/fdbclient/include/fdbclient/BlobCipher.h @@ -174,6 +174,11 @@ struct BlobCipherDetails { } bool operator!=(const BlobCipherDetails& o) const { return !(*this == o); } + bool isValid() const { + return this->encryptDomainId != INVALID_ENCRYPT_DOMAIN_ID && + this->baseCipherId != INVALID_ENCRYPT_CIPHER_KEY_ID && this->salt != INVALID_ENCRYPT_RANDOM_SALT; + } + template void serialize(Ar& ar) { serializer(ar, encryptDomainId, baseCipherId, salt); @@ -333,6 +338,15 @@ struct AesCtrNoAuthV1 { } }; +struct EncryptHeaderCipherDetails { + BlobCipherDetails textCipherDetails; + Optional headerCipherDetails; + + EncryptHeaderCipherDetails(const BlobCipherDetails& tCipherDetails) : textCipherDetails(tCipherDetails) {} + EncryptHeaderCipherDetails(const BlobCipherDetails& tCipherDetails, const BlobCipherDetails& hCipherDetails) + : textCipherDetails(tCipherDetails), headerCipherDetails(hCipherDetails) {} +}; + struct BlobCipherEncryptHeaderRef { // Serializable fields @@ -460,6 +474,9 @@ struct BlobCipherEncryptHeaderRef { } } + const uint8_t* getIV() const; + const EncryptHeaderCipherDetails getCipherDetails() const; + void validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails, const BlobCipherDetails& headerCipherDetails, const StringRef& ivRef) const; diff --git a/fdbserver/workloads/EncryptionOps.actor.cpp b/fdbserver/workloads/EncryptionOps.actor.cpp index ff087c5894..c1a3e06f9f 100644 --- a/fdbserver/workloads/EncryptionOps.actor.cpp +++ b/fdbserver/workloads/EncryptionOps.actor.cpp @@ -315,6 +315,24 @@ struct EncryptionOpsWorkload : TestWorkload { auto end = std::chrono::high_resolution_clock::now(); // validate encrypted buffer size and contents (not matching with plaintext) + const uint8_t* headerIV = headerRef->getIV(); + ASSERT_EQ(memcmp(&headerIV[0], &iv[0], AES_256_IV_LENGTH), 0); + + EncryptHeaderCipherDetails validateDetails = headerRef->getCipherDetails(); + ASSERT(validateDetails.textCipherDetails.isValid() && + validateDetails.textCipherDetails == BlobCipherDetails(textCipherKey->getDomainId(), + textCipherKey->getBaseCipherId(), + textCipherKey->getSalt())); + if (authMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) { + ASSERT(!validateDetails.headerCipherDetails.present()); + } else { + ASSERT(validateDetails.headerCipherDetails.present() && + validateDetails.headerCipherDetails.get().isValid() && + validateDetails.headerCipherDetails.get() == BlobCipherDetails(headerCipherKey->getDomainId(), + headerCipherKey->getBaseCipherId(), + headerCipherKey->getSalt())); + } + ASSERT_EQ(encrypted.size(), len); ASSERT_EQ(headerRef->flagsVersion, CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION); ASSERT_NE(memcmp(encrypted.begin(), payload, len), 0);