From dfdaa69b34b1030677b7049c6b717957d32c7af4 Mon Sep 17 00:00:00 2001 From: Attila Bukor Date: Mon, 13 Mar 2023 13:32:41 +0100 Subject: [PATCH] KUDU-3448 Plumbing for encrypting key material Key material for the internal PKI and token signing keys are stored in the syscatalog table in clear text, which is okay when volume-level encryption or Kudu's built-in data at rest encryption is used, but in some cases, this is either not used, or it's not enough (FISMA). To allow storing these key materials in encrypted form in the syscatalog table, this patch adds the necessary plumbing in Kudu's OpenSSL wrapper. It is now possible to pass a password callback function to the utility functions responsible for reading from and writing to OpenSSL BIO and strings. Change-Id: I24c5ac8ea0f9a4cab0f35ecccb1b7b00f3acefa8 Reviewed-on: http://gerrit.cloudera.org:8080/19615 Tested-by: Kudu Jenkins Reviewed-by: Alexey Serbin --- src/kudu/security/crypto.cc | 62 ++++++++++++++++++++++++++++++++ src/kudu/security/crypto.h | 5 +++ src/kudu/util/openssl_util_bio.h | 13 ++++--- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/kudu/security/crypto.cc b/src/kudu/security/crypto.cc index 1e0f07a449..be2ebf0f01 100644 --- a/src/kudu/security/crypto.cc +++ b/src/kudu/security/crypto.cc @@ -75,6 +75,12 @@ struct RsaPrivateKeyTraits : public SslTypeTraits { static constexpr auto kWritePemFunc = &PemWritePrivateKey; static constexpr auto kWriteDerFunc = &i2d_PrivateKey_bio; }; +struct RsaEncryptedPrivateKeyTraits : public SslTypeTraits { + static constexpr auto kReadPemFunc = &PEM_read_bio_PrivateKey; + static constexpr auto kReadDerFunc = &d2i_PKCS8PrivateKey_bio; + static constexpr auto kWritePemFunc = &PEM_write_bio_PKCS8PrivateKey; + static constexpr auto kWriteDerFunc = &i2d_PKCS8PrivateKey_bio; +}; struct RsaPublicKeyTraits : public SslTypeTraits { static constexpr auto kReadPemFunc = &PEM_read_bio_PUBKEY; static constexpr auto kReadDerFunc = &d2i_PUBKEY_bio; @@ -92,6 +98,50 @@ template<> struct SslTypeTraits { #endif }; +template<> +Status FromBIO(BIO* bio, DataFormat format, + c_unique_ptr* ret, const PasswordCallback& cb) { + CHECK(bio); + switch (format) { + case DataFormat::DER: + *ret = ssl_make_unique(RsaEncryptedPrivateKeyTraits::kReadDerFunc( + bio, nullptr, &TLSPasswordCB, const_cast(&cb))); + break; + case DataFormat::PEM: + *ret = ssl_make_unique(RsaEncryptedPrivateKeyTraits::kReadPemFunc( + bio, nullptr, &TLSPasswordCB, const_cast(&cb))); + break; + } + if (PREDICT_FALSE(!*ret)) { + return Status::RuntimeError("error reading private key", GetOpenSSLErrors()); + } + return Status::OK(); +} + +template<> +Status ToBIO(BIO* bio, DataFormat format, + EVP_PKEY* obj, const PasswordCallback& cb) { + CHECK(bio); + CHECK(obj); + switch (format) { + case DataFormat::DER: + OPENSSL_RET_NOT_OK(RsaEncryptedPrivateKeyTraits::kWriteDerFunc( + bio, obj, EVP_aes_256_cbc(), nullptr, 0, &TLSPasswordCB, + const_cast(&cb)), + "error exporting data in DER format"); + break; + case DataFormat::PEM: + OPENSSL_RET_NOT_OK(RsaEncryptedPrivateKeyTraits::kWritePemFunc( + bio, obj, EVP_aes_256_cbc(), nullptr, 0, &TLSPasswordCB, + const_cast(&cb)), + "error exporting data in PEM format"); + break; + } + OPENSSL_RET_NOT_OK(BIO_flush(bio), "error flushing BIO"); + return Status::OK(); +} + + namespace { const EVP_MD* GetMessageDigest(DigestType digest_type) { @@ -184,11 +234,23 @@ Status PrivateKey::FromString(const std::string& data, DataFormat format) { data, format, &data_); } +Status PrivateKey::FromEncryptedString(const std::string& data, DataFormat format, + const PasswordCallback& password_cb) { + return ::kudu::security::FromString( + data, format, &data_, password_cb); +} + Status PrivateKey::ToString(std::string* data, DataFormat format) const { return ::kudu::security::ToString( data, format, data_.get()); } +Status PrivateKey::ToEncryptedString(std::string* data, DataFormat format, + const PasswordCallback& password_cb) const { + return ::kudu::security::ToString( + data, format, data_.get(), password_cb); +} + Status PrivateKey::FromFile(const std::string& fpath, DataFormat format, const PasswordCallback& password_cb) { return ::kudu::security::FromFile( diff --git a/src/kudu/security/crypto.h b/src/kudu/security/crypto.h index db5d24f83f..51fdc2ec6a 100644 --- a/src/kudu/security/crypto.h +++ b/src/kudu/security/crypto.h @@ -75,6 +75,11 @@ class PrivateKey : public RawDataWrapper { Status FromString(const std::string& data, DataFormat format) WARN_UNUSED_RESULT; Status ToString(std::string* data, DataFormat format) const WARN_UNUSED_RESULT; + Status FromEncryptedString(const std::string& data, DataFormat format, + const PasswordCallback& password_cb) WARN_UNUSED_RESULT; + Status ToEncryptedString(std::string* data, DataFormat format, + const PasswordCallback& password_cb) const WARN_UNUSED_RESULT; + // If 'cb' is set, it will be called to obtain the password necessary to decrypt // the private key file in 'fpath'. Status FromFile(const std::string& fpath, DataFormat format, diff --git a/src/kudu/util/openssl_util_bio.h b/src/kudu/util/openssl_util_bio.h index 4eff7682e2..92861ac821 100644 --- a/src/kudu/util/openssl_util_bio.h +++ b/src/kudu/util/openssl_util_bio.h @@ -33,7 +33,8 @@ namespace security { template> -Status ToBIO(BIO* bio, DataFormat format, TYPE* obj) { +Status ToBIO(BIO* bio, DataFormat format, TYPE* obj, + const PasswordCallback& /*cb*/ = PasswordCallback()) { CHECK(bio); CHECK(obj); switch (format) { @@ -85,7 +86,8 @@ Status FromBIO(BIO* bio, DataFormat format, c_unique_ptr* ret, template> Status FromString(const std::string& data, DataFormat format, - c_unique_ptr* ret) { + c_unique_ptr* ret, + const PasswordCallback& cb = PasswordCallback()) { const void* mdata = reinterpret_cast(data.data()); auto bio = ssl_make_unique(BIO_new_mem_buf( #if OPENSSL_VERSION_NUMBER < 0x10002000L @@ -94,16 +96,17 @@ Status FromString(const std::string& data, DataFormat format, mdata, #endif data.size())); - RETURN_NOT_OK_PREPEND((FromBIO(bio.get(), format, ret)), + RETURN_NOT_OK_PREPEND((FromBIO(bio.get(), format, ret, cb)), "unable to load data from memory"); return Status::OK(); } template> -Status ToString(std::string* data, DataFormat format, Type* obj) { +Status ToString(std::string* data, DataFormat format, Type* obj, + const PasswordCallback& cb = PasswordCallback()) { CHECK(data); auto bio = ssl_make_unique(BIO_new(BIO_s_mem())); - RETURN_NOT_OK_PREPEND((ToBIO(bio.get(), format, obj)), + RETURN_NOT_OK_PREPEND((ToBIO(bio.get(), format, obj, cb)), "error serializing data"); BUF_MEM* membuf; OPENSSL_CHECK_OK(BIO_get_mem_ptr(bio.get(), &membuf));