This is an automated email from the ASF dual-hosted git repository. tarmstrong pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/impala.git
commit 6694d147002c9a713f486a9b58d1db74f0be2e85 Author: wzhou-code <wz...@cloudera.com> AuthorDate: Thu Oct 29 22:32:03 2020 -0700 IMPALA-10305: Sync Kudu's FIPS compliant changes kudu-3210 added FIPS compliant changes. This patch ports the following patches for kudu-3210 into Impala source tree: http://gerrit.cloudera.org:8080/16631/ http://gerrit.cloudera.org:8080/16657/ http://gerrit.cloudera.org:8080/16658/ http://gerrit.cloudera.org:8080/16659/ Testing: - Passed exhausive tests. Change-Id: I1aa5e69bf8470e3cfb316e8b6354ad29c28223f4 Reviewed-on: http://gerrit.cloudera.org:8080/16684 Reviewed-by: Impala Public Jenkins <impala-public-jenk...@cloudera.com> Tested-by: Impala Public Jenkins <impala-public-jenk...@cloudera.com> --- be/src/kudu/security/CMakeLists.txt | 2 +- be/src/kudu/security/ca/cert_management-test.cc | 3 +- be/src/kudu/security/crypto.cc | 13 ++++ be/src/kudu/security/openssl_util.cc | 80 +++++++++++++++++++------ be/src/kudu/security/openssl_util.h | 33 ++++++---- be/src/kudu/security/tls_context.cc | 20 ++++++- be/src/kudu/security/tls_handshake.cc | 24 ++++++-- be/src/kudu/security/token-test.cc | 2 +- be/src/kudu/util/flags.cc | 22 +++++++ be/src/kudu/util/flags.h | 2 + be/src/kudu/util/test_util.cc | 55 +++++++---------- be/src/kudu/util/test_util.h | 4 ++ 12 files changed, 187 insertions(+), 73 deletions(-) diff --git a/be/src/kudu/security/CMakeLists.txt b/be/src/kudu/security/CMakeLists.txt index 22e7442..9475e7d 100644 --- a/be/src/kudu/security/CMakeLists.txt +++ b/be/src/kudu/security/CMakeLists.txt @@ -73,9 +73,9 @@ set(SECURITY_SRCS ca/cert_management.cc cert.cc crypto.cc - kerberos_util.cc gssapi.cc init.cc + kerberos_util.cc openssl_util.cc ${PORTED_X509_CHECK_HOST_CC} security_flags.cc diff --git a/be/src/kudu/security/ca/cert_management-test.cc b/be/src/kudu/security/ca/cert_management-test.cc index 0c8abc8..f3e6c35 100644 --- a/be/src/kudu/security/ca/cert_management-test.cc +++ b/be/src/kudu/security/ca/cert_management-test.cc @@ -71,7 +71,8 @@ class CertManagementTest : public KuduTest { template<class CSRGen = CertRequestGenerator> CertSignRequest PrepareTestCSR(typename CSRGen::Config config, PrivateKey* key) { - CHECK_OK(GeneratePrivateKey(512, key)); + int key_size = UseLargeKeys() ? 2048 : 512; + CHECK_OK(GeneratePrivateKey(key_size, key)); CSRGen gen(std::move(config)); CHECK_OK(gen.Init()); CertSignRequest req; diff --git a/be/src/kudu/security/crypto.cc b/be/src/kudu/security/crypto.cc index 234d193..5e6fd16 100644 --- a/be/src/kudu/security/crypto.cc +++ b/be/src/kudu/security/crypto.cc @@ -18,6 +18,9 @@ #include "kudu/security/crypto.h" #include <memory> +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include <mutex> +#endif #include <ostream> #include <string> @@ -67,6 +70,10 @@ int DerWritePublicKey(BIO* bio, EVP_PKEY* key) { return i2d_RSA_PUBKEY_bio(bio, rsa.get()); } +#if OPENSSL_VERSION_NUMBER < 0x10100000L +OpenSSLMutex mutex; +#endif + } // anonymous namespace template<> struct SslTypeTraits<BIGNUM> { @@ -136,6 +143,9 @@ Status PublicKey::VerifySignature(DigestType digest, const EVP_MD* md = GetMessageDigest(digest); auto md_ctx = ssl_make_unique(EVP_MD_CTX_create()); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + std::unique_lock<OpenSSLMutex> l(mutex); +#endif OPENSSL_RET_NOT_OK(EVP_DigestVerifyInit(md_ctx.get(), nullptr, md, nullptr, GetRawData()), "error initializing verification digest"); OPENSSL_RET_NOT_OK(EVP_DigestVerifyUpdate(md_ctx.get(), data.data(), data.size()), @@ -228,6 +238,9 @@ Status PrivateKey::MakeSignature(DigestType digest, const EVP_MD* md = GetMessageDigest(digest); auto md_ctx = ssl_make_unique(EVP_MD_CTX_create()); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + std::unique_lock<OpenSSLMutex> l(mutex); +#endif OPENSSL_RET_NOT_OK(EVP_DigestSignInit(md_ctx.get(), nullptr, md, nullptr, GetRawData()), "error initializing signing digest"); OPENSSL_RET_NOT_OK(EVP_DigestSignUpdate(md_ctx.get(), data.data(), data.size()), diff --git a/be/src/kudu/security/openssl_util.cc b/be/src/kudu/security/openssl_util.cc index e96fcb3..7198db3 100644 --- a/be/src/kudu/security/openssl_util.cc +++ b/be/src/kudu/security/openssl_util.cc @@ -17,6 +17,10 @@ #include "kudu/security/openssl_util.h" +#include <openssl/crypto.h> +#include <openssl/err.h> +#include <openssl/rand.h> // IWYU pragma: keep + #include <cerrno> #include <cstdint> #include <cstdio> @@ -24,21 +28,35 @@ #include <string> #include <vector> +#include <gflags/gflags.h> #include <glog/logging.h> -#include <openssl/crypto.h> -#include <openssl/err.h> -#include <openssl/rand.h> +#include "kudu/gutil/macros.h" #include "kudu/gutil/strings/split.h" #include "kudu/gutil/strings/strip.h" #include "kudu/gutil/strings/substitute.h" +#if OPENSSL_VERSION_NUMBER < 0x10100000L #include "kudu/util/debug/leakcheck_disabler.h" +#endif #include "kudu/util/errno.h" +#include "kudu/util/flag_tags.h" +#include "kudu/util/flags.h" +#if OPENSSL_VERSION_NUMBER < 0x10100000L #include "kudu/util/mutex.h" +#endif #include "kudu/util/scoped_cleanup.h" #include "kudu/util/status.h" #include "kudu/util/subprocess.h" +DEFINE_bool(openssl_defensive_locking, + false, + "If enabled, cryptographic methods lock more defensively to work around thread safety " + "issues in certain OpenSSL versions. This makes Kudu servers or clients more stable " + "when running on an affected OpenSSL version, sacrificing some performance."); +TAG_FLAG(openssl_defensive_locking, unsafe); +TAG_FLAG(openssl_defensive_locking, hidden); +TAG_FLAG(openssl_defensive_locking, runtime); + using std::ostringstream; using std::string; using std::vector; @@ -81,6 +99,18 @@ void LockingCB(int mode, int type, const char* /*file*/, int /*line*/) { } #endif +void CheckFIPSMode() { + auto fips_mode = FIPS_mode(); + // If the environment variable KUDU_REQUIRE_FIPS_MODE is set to "1", we + // check if FIPS approved mode is enabled. If not, we crash the process. + // As this is used in clients as well, we can't use gflags to set this. + if (GetBooleanEnvironmentVariable("KUDU_REQUIRE_FIPS_MODE")) { + CHECK(fips_mode) << "FIPS mode required by environment variable " + "KUDU_REQUIRE_FIPS_MODE, but it is not enabled."; + } + VLOG(2) << "FIPS mode is " << (fips_mode ? "enabled" : "disabled."); +} + Status CheckOpenSSLInitialized() { #if OPENSSL_VERSION_NUMBER < 0x10100000L // Starting with OpenSSL 1.1.0, the old thread API became obsolete @@ -112,10 +142,18 @@ Status CheckOpenSSLInitialized() { "SSL library appears uninitialized (cannot create SSL_CTX)"); } #endif + CheckFIPSMode(); return Status::OK(); } void DoInitializeOpenSSL() { + // In case the user's thread has left some error around, clear it. + ERR_clear_error(); + SCOPED_OPENSSL_NO_PENDING_ERRORS; + if (g_disable_ssl_init) { + VLOG(2) << "Not initializing OpenSSL (disabled by application)"; + return; + } #if OPENSSL_VERSION_NUMBER >= 0x10100000L // The OPENSSL_init_ssl manpage [1] says "As of version 1.1.0 OpenSSL will // automatically allocate all resources it needs so no explicit initialisation @@ -131,21 +169,8 @@ void DoInitializeOpenSSL() { // // 1. https://www.openssl.org/docs/man1.1.0/ssl/OPENSSL_init_ssl.html // 2. https://github.com/openssl/openssl/issues/5899 - if (g_disable_ssl_init) { - VLOG(2) << "Not initializing OpenSSL (disabled by application)"; - return; - } CHECK_EQ(1, OPENSSL_init_ssl(0, nullptr)); - SCOPED_OPENSSL_NO_PENDING_ERRORS; #else - // In case the user's thread has left some error around, clear it. - ERR_clear_error(); - SCOPED_OPENSSL_NO_PENDING_ERRORS; - if (g_disable_ssl_init) { - VLOG(2) << "Not initializing OpenSSL (disabled by application)"; - return; - } - // Check that OpenSSL isn't already initialized. If it is, it's likely // we are embedded in (or embedding) another application/library which // initializes OpenSSL, and we risk installing conflicting callbacks @@ -186,7 +211,7 @@ void DoInitializeOpenSSL() { CRYPTO_set_locking_callback(LockingCB); } #endif - + CheckFIPSMode(); g_ssl_is_initialized = true; } @@ -345,5 +370,26 @@ Status GetPasswordFromShellCommand(const string& cmd, string* password) { return Status::OK(); } +OpenSSLMutex::OpenSSLMutex() : locking_enabled_(FLAGS_openssl_defensive_locking) {} + +void OpenSSLMutex::lock() { + if (locking_enabled_) { + mutex_.lock(); + } +} + +bool OpenSSLMutex::try_lock() { + if (locking_enabled_) { + return mutex_.try_lock(); + } + return true; +} + +void OpenSSLMutex::unlock() { + if (locking_enabled_) { + mutex_.unlock(); + } +} + } // namespace security } // namespace kudu diff --git a/be/src/kudu/security/openssl_util.h b/be/src/kudu/security/openssl_util.h index 00c4ebf..356ecb0 100644 --- a/be/src/kudu/security/openssl_util.h +++ b/be/src/kudu/security/openssl_util.h @@ -19,6 +19,7 @@ #include <functional> #include <memory> +#include <mutex> #include <ostream> #include <string> @@ -189,29 +190,39 @@ class RawDataWrapper { c_unique_ptr<RawDataType> data_; }; +// Wrapper around std::mutex that only locks if a special +// 'openssl_defensive_locking' flag is set to true. See the description of the +// flag for more details. +class OpenSSLMutex { + public: + OpenSSLMutex(); + void lock(); + bool try_lock(); + void unlock(); -namespace internal { + private: + std::mutex mutex_; + const bool locking_enabled_; +}; +namespace internal { // Implementation of SCOPED_OPENSSL_NO_PENDING_ERRORS. Use the macro form // instead of directly instantiating the implementation class. struct ScopedCheckNoPendingSSLErrors { public: - explicit ScopedCheckNoPendingSSLErrors(const char* func) - : func_(func) { - DCHECK_EQ(ERR_peek_error(), 0) - << "Expected no pending OpenSSL errors on " << func_ - << " entry, but had: " << GetOpenSSLErrors(); + explicit ScopedCheckNoPendingSSLErrors(const char* func) : func_(func) { + DCHECK_EQ(ERR_peek_error(), 0) << "Expected no pending OpenSSL errors on " << func_ + << " entry, but had: " << GetOpenSSLErrors(); } ~ScopedCheckNoPendingSSLErrors() { - DCHECK_EQ(ERR_peek_error(), 0) - << "Expected no pending OpenSSL errors on " << func_ - << " exit, but had: " << GetOpenSSLErrors(); + DCHECK_EQ(ERR_peek_error(), 0) << "Expected no pending OpenSSL errors on " << func_ + << " exit, but had: " << GetOpenSSLErrors(); } private: const char* const func_; }; -} // namespace internal -} // namespace security +} // namespace internal +} // namespace security } // namespace kudu diff --git a/be/src/kudu/security/tls_context.cc b/be/src/kudu/security/tls_context.cc index a01b779..622165f 100644 --- a/be/src/kudu/security/tls_context.cc +++ b/be/src/kudu/security/tls_context.cc @@ -21,6 +21,7 @@ #include <mutex> #include <ostream> #include <string> +#include <type_traits> #include <vector> #include <boost/algorithm/string/predicate.hpp> @@ -81,6 +82,12 @@ DEFINE_int32(ipki_server_key_size, 2048, "is used for TLS connections to and from clients and other servers."); TAG_FLAG(ipki_server_key_size, experimental); +#if OPENSSL_VERSION_NUMBER < 0x10100000L +namespace { +kudu::security::OpenSSLMutex mutex; +} // anonymous namespace +#endif + namespace kudu { namespace security { @@ -212,9 +219,13 @@ Status TlsContext::VerifyCertChainUnlocked(const Cert& cert) { int rc = X509_verify_cert(store_ctx.get()); if (rc != 1) { int err = X509_STORE_CTX_get_error(store_ctx.get()); + + // This also clears the errors. It's important to do this before we call + // X509NameToString(), as it expects the error stack to be empty and it + // calls SCOPED_OPENSSL_NO_PENDING_ERRORS. + const auto error_msg = GetOpenSSLErrors(); if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) { // It's OK to provide a self-signed cert. - ERR_clear_error(); // in case it left anything on the queue. return Status::OK(); } @@ -227,9 +238,8 @@ Status TlsContext::VerifyCertChainUnlocked(const Cert& cert) { X509NameToString(X509_get_issuer_name(cur_cert))); } - ERR_clear_error(); // in case it left anything on the queue. return Status::RuntimeError( - Substitute("could not verify certificate chain$0", cert_details), + Substitute("could not verify certificate chain$0: $1", cert_details, error_msg), X509_verify_cert_error_string(err)); } return Status::OK(); @@ -423,6 +433,10 @@ boost::optional<CertSignRequest> TlsContext::GetCsrIfNecessary() const { Status TlsContext::AdoptSignedCert(const Cert& cert) { SCOPED_OPENSSL_NO_PENDING_ERRORS; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + unique_lock<OpenSSLMutex> lock_global(mutex); +#endif unique_lock<RWMutex> lock(lock_); if (!csr_) { diff --git a/be/src/kudu/security/tls_handshake.cc b/be/src/kudu/security/tls_handshake.cc index 5a89592..d61da6d 100644 --- a/be/src/kudu/security/tls_handshake.cc +++ b/be/src/kudu/security/tls_handshake.cc @@ -17,16 +17,20 @@ #include "kudu/security/tls_handshake.h" -#include <memory> -#include <string> - #include <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/x509v3.h> +#include <memory> +#if OPENSSL_VERSION_NUMBER < 0x10002000L +#include <mutex> +#endif +#include <string> + #include "kudu/gutil/strings/strip.h" #include "kudu/gutil/strings/substitute.h" #include "kudu/security/cert.h" +#include "kudu/security/openssl_util.h" #include "kudu/security/tls_socket.h" #include "kudu/util/net/socket.h" #include "kudu/util/status.h" @@ -40,6 +44,12 @@ using std::string; using std::unique_ptr; using strings::Substitute; +#if OPENSSL_VERSION_NUMBER < 0x10100000L +namespace { +kudu::security::OpenSSLMutex mutex; +} // anonymous namespace +#endif + namespace kudu { namespace security { @@ -92,7 +102,13 @@ Status TlsHandshake::Continue(const string& recv, string* send) { DCHECK(n == recv.size() || (n == -1 && recv.empty())); DCHECK_EQ(BIO_ctrl_pending(rbio), recv.size()); - int rc = SSL_do_handshake(ssl_.get()); + int rc; + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + std::unique_lock<OpenSSLMutex> lock(mutex); +#endif + rc = SSL_do_handshake(ssl_.get()); + } if (rc != 1) { int ssl_err = SSL_get_error(ssl_.get(), rc); // WANT_READ and WANT_WRITE indicate that the handshake is not yet complete. diff --git a/be/src/kudu/security/token-test.cc b/be/src/kudu/security/token-test.cc index a4d7804..00e4f61 100644 --- a/be/src/kudu/security/token-test.cc +++ b/be/src/kudu/security/token-test.cc @@ -57,7 +57,7 @@ namespace security { namespace { // Dummy variables to use when their values don't matter much. -const int kNumBits = 512; +const int kNumBits = UseLargeKeys() ? 2048 : 512; const int64_t kTokenValiditySeconds = 10; const char kUser[] = "user"; diff --git a/be/src/kudu/util/flags.cc b/be/src/kudu/util/flags.cc index 97519c8..dfd2b14 100644 --- a/be/src/kudu/util/flags.cc +++ b/be/src/kudu/util/flags.cc @@ -17,10 +17,12 @@ #include "kudu/util/flags.h" +#include <strings.h> #include <sys/stat.h> #include <unistd.h> // IWYU pragma: keep #include <cstdlib> +#include <cstring> #include <functional> #include <iostream> #include <map> @@ -596,4 +598,24 @@ Status ParseTriState(const char* flag_name, const std::string& flag_value, return Status::OK(); } +// Get the value of an environment variable that has boolean semantics. +bool GetBooleanEnvironmentVariable(const char* env_var_name) { + const char* const e = getenv(env_var_name); + if ((e == nullptr) || + (strlen(e) == 0) || + (strcasecmp(e, "false") == 0) || + (strcasecmp(e, "0") == 0) || + (strcasecmp(e, "no") == 0)) { + return false; + } + if ((strcasecmp(e, "true") == 0) || + (strcasecmp(e, "1") == 0) || + (strcasecmp(e, "yes") == 0)) { + return true; + } + LOG(FATAL) << Substitute("$0: invalid value for environment variable $0", + e, env_var_name); + return false; // unreachable +} + } // namespace kudu diff --git a/be/src/kudu/util/flags.h b/be/src/kudu/util/flags.h index 033a57f..98f8cb8 100644 --- a/be/src/kudu/util/flags.h +++ b/be/src/kudu/util/flags.h @@ -90,5 +90,7 @@ Status ParseTriState(const char* flag_name, const std::string& flag_value, std::string CheckFlagAndRedact(const google::CommandLineFlagInfo& flag, EscapeMode mode); +bool GetBooleanEnvironmentVariable(const char* env_var_name); + } // namespace kudu #endif /* KUDU_UTIL_FLAGS_H */ diff --git a/be/src/kudu/util/test_util.cc b/be/src/kudu/util/test_util.cc index 93eac65..90ed621 100644 --- a/be/src/kudu/util/test_util.cc +++ b/be/src/kudu/util/test_util.cc @@ -21,8 +21,8 @@ #include <limits.h> #include <unistd.h> +#include <cerrno> #include <cstdlib> -#include <cstring> #include <limits> #include <map> #include <memory> @@ -51,6 +51,7 @@ #include "kudu/gutil/walltime.h" #include "kudu/util/env.h" #include "kudu/util/faststring.h" +#include "kudu/util/flags.h" #include "kudu/util/path_util.h" #include "kudu/util/scoped_cleanup.h" #include "kudu/util/slice.h" @@ -76,6 +77,7 @@ namespace kudu { const char* kInvalidPath = "/dev/invalid-path-for-kudu-tests"; static const char* const kSlowTestsEnvVar = "KUDU_ALLOW_SLOW_TESTS"; +static const char* const kLargeKeysEnvVar = "KUDU_USE_LARGE_KEYS_IN_TESTS"; static const uint64_t kTestBeganAtMicros = Env::Default()->NowMicros(); @@ -100,21 +102,26 @@ KuduTest::KuduTest() {"never_fsync", "true"}, // Disable redaction. {"redact", "none"}, - // Reduce default RSA key length for faster tests. We are using strong/high - // TLS v1.2 cipher suites, so minimum possible for TLS-related RSA keys is - // 768 bits. However, for the external mini cluster we use 1024 bits because - // Java default security policies require at least 1024 bits for RSA keys - // used in certificates. For uniformity, here 1024 RSA bit keys are used - // as well. As for the TSK keys, 512 bits is the minimum since the SHA256 - // digest is used for token signing/verification. - {"ipki_server_key_size", "1024"}, - {"ipki_ca_key_size", "1024"}, - {"tsk_num_rsa_bits", "512"}, // For a generic Kudu test, the local wall-clock time is good enough even // if it's not synchronized by NTP. All test components are run at the same // node, so there aren't multiple time sources to synchronize. {"time_source", "system_unsync"}, }; + if (!UseLargeKeys()) { + // Reduce default RSA key length for faster tests. We are using strong/high + // TLS v1.2 cipher suites, so minimum possible for TLS-related RSA keys is + // 768 bits. Java security policies in tests tweaked appropriately to allow + // for using smaller RSA keys in certificates. As for the TSK keys, 512 bits + // is the minimum since the SHA256 digest is used for token + // signing/verification. + flags_for_tests.emplace("ipki_server_key_size", "768"); + flags_for_tests.emplace("ipki_ca_key_size", "768"); + flags_for_tests.emplace("tsk_num_rsa_bits", "512"); + // Some OS distros set the default security level higher than 0, so it's + // necessary to override it to use the key length specified above (which are + // considered lax and don't work in case of security level 2 or higher). + flags_for_tests.emplace("openssl_security_level_override", "0"); + } for (const auto& e : flags_for_tests) { // We don't check for errors here, because we have some default flags that // only apply to certain tests. If a flag is defined in a library which @@ -177,31 +184,9 @@ void KuduTest::OverrideKrb5Environment() { // Test utility functions /////////////////////////////////////////////////// -namespace { -// Get the value of an environment variable that has boolean semantics. -bool GetBooleanEnvironmentVariable(const char* env_var_name) { - const char* const e = getenv(env_var_name); - if ((e == nullptr) || - (strlen(e) == 0) || - (strcasecmp(e, "false") == 0) || - (strcasecmp(e, "0") == 0) || - (strcasecmp(e, "no") == 0)) { - return false; - } - if ((strcasecmp(e, "true") == 0) || - (strcasecmp(e, "1") == 0) || - (strcasecmp(e, "yes") == 0)) { - return true; - } - LOG(FATAL) << Substitute("$0: invalid value for environment variable $0", - e, env_var_name); - return false; // unreachable -} -} // anonymous namespace +bool AllowSlowTests() { return GetBooleanEnvironmentVariable(kSlowTestsEnvVar); } -bool AllowSlowTests() { - return GetBooleanEnvironmentVariable(kSlowTestsEnvVar); -} +bool UseLargeKeys() { return GetBooleanEnvironmentVariable(kLargeKeysEnvVar); } void OverrideFlagForSlowTests(const std::string& flag_name, const std::string& new_value) { diff --git a/be/src/kudu/util/test_util.h b/be/src/kudu/util/test_util.h index d320e3e..d9f657a 100644 --- a/be/src/kudu/util/test_util.h +++ b/be/src/kudu/util/test_util.h @@ -87,6 +87,10 @@ class KuduTest : public ::testing::Test { // Returns true if slow tests are runtime-enabled. bool AllowSlowTests(); +// Returns true if the KUDU_USE_LARGE_KEYS_IN_TESTS environment variable is set +// to true. This is required to pass certain tests in FIPS approved mode. +bool UseLargeKeys(); + // Override the given gflag to the new value, only in the case that // slow tests are enabled and the user hasn't otherwise overridden // it on the command line.