EaR: Helper routines to support configurable encryption (#9368)

* EaR: Helper routines to support configurable encryption

Description

Add helper methods to BlobCipherEncryptHeaderRef enabling:
1. Extract 'IV' abstracting out underlying algorithm header
1. Extract 'cipherDetails' abstracting out underlying algorithm header

Testing

BlobCipherUnitTest & EncryptionOps are updated - 100K loop

* EaR: Helper routines to support configurable encryption

Description

Add helper methods to BlobCipherEncryptHeaderRef enabling:
1. Extract 'IV' abstracting out underlying algorithm header
1. Extract 'cipherDetails' abstracting out underlying algorithm header

Testing

BlobCipherUnitTest & EncryptionOps are updated - 100K loop
This commit is contained in:
Ata E Husain Bohra 2023-02-14 08:34:41 -08:00 committed by GitHub
parent 05e73b7836
commit 401b9c8918
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 48 deletions

View File

@ -60,6 +60,58 @@
#define BLOB_CIPHER_DEBUG false #define BLOB_CIPHER_DEBUG false
#define BLOB_CIPHER_SERIALIZATION_CHECKS 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 // BlobCipherEncryptHeaderRef
uint32_t BlobCipherEncryptHeaderRef::getHeaderSize(const int flagVersion, uint32_t BlobCipherEncryptHeaderRef::getHeaderSize(const int flagVersion,
@ -91,62 +143,93 @@ uint32_t BlobCipherEncryptHeaderRef::getHeaderSize(const int flagVersion,
return total; return total;
} }
const uint8_t* BlobCipherEncryptHeaderRef::getIV() const {
ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION);
validateEncryptHeaderFlagVersion(flagsVersion);
ASSERT_EQ(flagsVersion, 1);
BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(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 <class>
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<BlobCipherEncryptHeaderFlagsV1>(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<decltype(h)>;
if constexpr (std::is_same_v<T, AesCtrNoAuthV1>) {
return EncryptHeaderCipherDetails(h.cipherTextDetails);
} else if constexpr (std::is_same_v<T, AesCtrWithAuthV1<AUTH_TOKEN_HMAC_SHA_SIZE>> ||
std::is_same_v<T, AesCtrWithAuthV1<AUTH_TOKEN_AES_CMAC_SIZE>>) {
return EncryptHeaderCipherDetails(h.cipherTextDetails, h.cipherHeaderDetails);
} else {
static_assert(always_false_v<T>, "Unknown encryption authentication");
}
},
algoHeader);
}
void BlobCipherEncryptHeaderRef::validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails, void BlobCipherEncryptHeaderRef::validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails,
const BlobCipherDetails& headerCipherDetails, const BlobCipherDetails& headerCipherDetails,
const StringRef& ivRef) const { const StringRef& ivRef) const {
ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION); ASSERT(CLIENT_KNOBS->ENABLE_CONFIGURABLE_ENCRYPTION);
if (flagsVersion > CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION) { validateEncryptHeaderFlagVersion(flagsVersion);
TraceEvent("ValidateEncryptHeaderUnsupportedFlagVersion") ASSERT_EQ(flagsVersion, 1);
.detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION)
.detail("Version", flagsVersion);
throw not_implemented();
}
BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(this->flags); BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(this->flags);
validateEncryptHeaderAlgoHeaderVersion((EncryptCipherMode)flags.encryptMode,
(EncryptAuthTokenMode)flags.authTokenMode,
(EncryptAuthTokenAlgo)flags.authTokenAlgo,
algoHeaderVersion);
ASSERT_EQ(algoHeaderVersion, 1);
BlobCipherDetails persistedTextCipherDetails; BlobCipherDetails persistedTextCipherDetails;
BlobCipherDetails persistedHeaderCipherDetails; BlobCipherDetails persistedHeaderCipherDetails;
uint8_t* persistedIV = nullptr; uint8_t* persistedIV = nullptr;
if (flags.authTokenMode == ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE) { // TODO: Replace with "Overload visitor pattern" someday.
if (algoHeaderVersion > CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) { return std::visit(
TraceEvent("ValidateEncryptHeaderUnsupportedAlgoHeaderVersion") [&persistedTextCipherDetails, &persistedHeaderCipherDetails, &persistedIV](auto&& h) {
.detail("AuthMode", "No-Auth") using T = std::decay_t<decltype(h)>;
.detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) if constexpr (std::is_same_v<T, AesCtrNoAuthV1>) {
.detail("Version", algoHeaderVersion); persistedTextCipherDetails = h.cipherTextDetails;
throw not_implemented(); persistedIV = (uint8_t*)&h.iv[0];
} } else if constexpr (std::is_same_v<T, AesCtrWithAuthV1<AUTH_TOKEN_HMAC_SHA_SIZE>> ||
persistedTextCipherDetails = std::get<AesCtrNoAuthV1>(this->algoHeader).cipherTextDetails; std::is_same_v<T, AesCtrWithAuthV1<AUTH_TOKEN_AES_CMAC_SIZE>>) {
persistedIV = (uint8_t*)(&std::get<AesCtrNoAuthV1>(this->algoHeader).iv[0]); persistedTextCipherDetails = h.cipherTextDetails;
} else { persistedHeaderCipherDetails = h.cipherHeaderDetails;
if (flags.authTokenAlgo == ENCRYPT_HEADER_AUTH_TOKEN_ALGO_HMAC_SHA) { persistedIV = (uint8_t*)&h.iv[0];
if (algoHeaderVersion > CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_NO_AUTH_VERSION) { } else {
TraceEvent("ValidateEncryptHeaderUnsupportedAlgoHeaderVersion") static_assert(always_false_v<T>, "Unknown encryption authentication");
.detail("AuthMode", "Hmac-Sha") }
.detail("MaxSupportedVersion", CLIENT_KNOBS->ENCRYPT_HEADER_AES_CTR_HMAC_SHA_AUTH_VERSION) },
.detail("Version", algoHeaderVersion); algoHeader);
}
persistedTextCipherDetails =
std::get<AesCtrWithAuthV1<AUTH_TOKEN_HMAC_SHA_SIZE>>(this->algoHeader).cipherTextDetails;
persistedHeaderCipherDetails =
std::get<AesCtrWithAuthV1<AUTH_TOKEN_HMAC_SHA_SIZE>>(this->algoHeader).cipherHeaderDetails;
persistedIV = (uint8_t*)(&std::get<AesCtrWithAuthV1<AUTH_TOKEN_HMAC_SHA_SIZE>>(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<AesCtrWithAuthV1<AUTH_TOKEN_AES_CMAC_SIZE>>(this->algoHeader).cipherTextDetails;
persistedHeaderCipherDetails =
std::get<AesCtrWithAuthV1<AUTH_TOKEN_AES_CMAC_SIZE>>(this->algoHeader).cipherHeaderDetails;
persistedIV = (uint8_t*)(&std::get<AesCtrWithAuthV1<AUTH_TOKEN_AES_CMAC_SIZE>>(this->algoHeader).iv[0]);
} else {
throw not_implemented();
}
}
// Validate encryption header 'cipherHeader' details sanity // Validate encryption header 'cipherHeader' details sanity
if (flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE && if (flags.authTokenMode != ENCRYPT_HEADER_AUTH_TOKEN_MODE_NONE &&
@ -1808,8 +1891,17 @@ void testConfigurableEncryptionHeaderNoAuthMode(const int minDomainId) {
BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(headerRef.flags); BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(headerRef.flags);
AesCtrNoAuthV1 noAuth = std::get<AesCtrNoAuthV1>(headerRef.algoHeader); AesCtrNoAuthV1 noAuth = std::get<AesCtrNoAuthV1>(headerRef.algoHeader);
Standalone<StringRef> 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<StringRef> serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef);
BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef); BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef);
BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get<BlobCipherEncryptHeaderFlagsV1>(validateHeader.flags); BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get<BlobCipherEncryptHeaderFlagsV1>(validateHeader.flags);
ASSERT(validateFlags == flags); ASSERT(validateFlags == flags);
@ -2093,8 +2185,20 @@ void testConfigurableEncryptionHeaderSingleAuthMode(int minDomainId) {
BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(headerRef.flags); BlobCipherEncryptHeaderFlagsV1 flags = std::get<BlobCipherEncryptHeaderFlagsV1>(headerRef.flags);
AesCtrWithAuthV1<AuthTokenSize> algoHeader = std::get<AesCtrWithAuthV1<AuthTokenSize>>(headerRef.algoHeader); AesCtrWithAuthV1<AuthTokenSize> algoHeader = std::get<AesCtrWithAuthV1<AuthTokenSize>>(headerRef.algoHeader);
Standalone<StringRef> 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<StringRef> serHeaderRef = BlobCipherEncryptHeaderRef::toStringRef(headerRef);
BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef); BlobCipherEncryptHeaderRef validateHeader = BlobCipherEncryptHeaderRef::fromStringRef(serHeaderRef);
BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get<BlobCipherEncryptHeaderFlagsV1>(validateHeader.flags); BlobCipherEncryptHeaderFlagsV1 validateFlags = std::get<BlobCipherEncryptHeaderFlagsV1>(validateHeader.flags);
ASSERT(validateFlags == 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(&iv[0], &validateAlgo.iv[0], AES_256_IV_LENGTH), 0);
ASSERT_EQ(memcmp(&algoHeader.authToken[0], &validateAlgo.authToken[0], AuthTokenSize), 0); ASSERT_EQ(memcmp(&algoHeader.authToken[0], &validateAlgo.authToken[0], AuthTokenSize), 0);
TraceEvent("HmacShaHeaderSize") TraceEvent("HeaderSize")
.detail("Flags", sizeof(flags)) .detail("Flags", sizeof(flags))
.detail("AlgoHeader", sizeof(algoHeader)) .detail("AlgoHeader", sizeof(algoHeader))
.detail("TotalHeader", serHeaderRef.size()); .detail("TotalHeader", serHeaderRef.size());

View File

@ -174,6 +174,11 @@ struct BlobCipherDetails {
} }
bool operator!=(const BlobCipherDetails& o) const { return !(*this == o); } 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 <class Ar> template <class Ar>
void serialize(Ar& ar) { void serialize(Ar& ar) {
serializer(ar, encryptDomainId, baseCipherId, salt); serializer(ar, encryptDomainId, baseCipherId, salt);
@ -333,6 +338,15 @@ struct AesCtrNoAuthV1 {
} }
}; };
struct EncryptHeaderCipherDetails {
BlobCipherDetails textCipherDetails;
Optional<BlobCipherDetails> headerCipherDetails;
EncryptHeaderCipherDetails(const BlobCipherDetails& tCipherDetails) : textCipherDetails(tCipherDetails) {}
EncryptHeaderCipherDetails(const BlobCipherDetails& tCipherDetails, const BlobCipherDetails& hCipherDetails)
: textCipherDetails(tCipherDetails), headerCipherDetails(hCipherDetails) {}
};
struct BlobCipherEncryptHeaderRef { struct BlobCipherEncryptHeaderRef {
// Serializable fields // Serializable fields
@ -460,6 +474,9 @@ struct BlobCipherEncryptHeaderRef {
} }
} }
const uint8_t* getIV() const;
const EncryptHeaderCipherDetails getCipherDetails() const;
void validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails, void validateEncryptionHeaderDetails(const BlobCipherDetails& textCipherDetails,
const BlobCipherDetails& headerCipherDetails, const BlobCipherDetails& headerCipherDetails,
const StringRef& ivRef) const; const StringRef& ivRef) const;

View File

@ -315,6 +315,24 @@ struct EncryptionOpsWorkload : TestWorkload {
auto end = std::chrono::high_resolution_clock::now(); auto end = std::chrono::high_resolution_clock::now();
// validate encrypted buffer size and contents (not matching with plaintext) // 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(encrypted.size(), len);
ASSERT_EQ(headerRef->flagsVersion, CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION); ASSERT_EQ(headerRef->flagsVersion, CLIENT_KNOBS->ENCRYPT_HEADER_FLAGS_VERSION);
ASSERT_NE(memcmp(encrypted.begin(), payload, len), 0); ASSERT_NE(memcmp(encrypted.begin(), payload, len), 0);