diff --git a/.gitmodules b/.gitmodules index 0805b6d5492..7c5f5e73dca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -294,3 +294,6 @@ [submodule "contrib/libdivide"] path = contrib/libdivide url = https://github.com/ridiculousfish/libdivide.git +[submodule "contrib/libbcrypt"] + path = contrib/libbcrypt + url = https://github.com/rg3/libbcrypt.git diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 6f80059498e..52db1c57bdf 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -170,6 +170,8 @@ add_contrib (annoy-cmake annoy) add_contrib (xxHash-cmake xxHash) +add_contrib (libbcrypt-cmake libbcrypt) + add_contrib (google-benchmark-cmake google-benchmark) # Put all targets defined here and in subdirectories under "contrib/" folders in GUI-based IDEs. diff --git a/contrib/libbcrypt b/contrib/libbcrypt new file mode 160000 index 00000000000..8aa32ad94eb --- /dev/null +++ b/contrib/libbcrypt @@ -0,0 +1 @@ +Subproject commit 8aa32ad94ebe06b76853b0767c910c9fbf7ccef4 diff --git a/contrib/libbcrypt-cmake/CMakeLists.txt b/contrib/libbcrypt-cmake/CMakeLists.txt new file mode 100644 index 00000000000..d40d7f9195e --- /dev/null +++ b/contrib/libbcrypt-cmake/CMakeLists.txt @@ -0,0 +1,19 @@ +option(ENABLE_BCRYPT "Enable bcrypt" ${ENABLE_LIBRARIES}) + +if (NOT ENABLE_BCRYPT) + message(STATUS "Not using bcrypt") + return() +endif() + +set (LIBRARY_DIR "${ClickHouse_SOURCE_DIR}/contrib/libbcrypt") + +set(SRCS + "${LIBRARY_DIR}/bcrypt.c" + "${LIBRARY_DIR}/crypt_blowfish/crypt_blowfish.c" + "${LIBRARY_DIR}/crypt_blowfish/crypt_gensalt.c" + "${LIBRARY_DIR}/crypt_blowfish/wrapper.c" +) + +add_library(_bcrypt ${SRCS}) +target_include_directories(_bcrypt SYSTEM PUBLIC "${LIBRARY_DIR}") +add_library(ch_contrib::bcrypt ALIAS _bcrypt) diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index c6bbd421c77..650d1fb5a29 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -31,6 +31,11 @@ namespace return (Util::encodeDoubleSHA1(password) == password_double_sha1); } + bool checkPasswordBcrypt(std::string_view password, const Digest & password_bcrypt) + { + return Util::checkPasswordBcrypt(password, password_bcrypt); + } + bool checkPasswordSHA256(std::string_view password, const Digest & password_sha256, const String & salt) { return Util::encodeSHA256(String(password).append(salt)) == password_sha256; @@ -81,6 +86,7 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const case AuthenticationType::PLAINTEXT_PASSWORD: case AuthenticationType::SHA256_PASSWORD: case AuthenticationType::DOUBLE_SHA1_PASSWORD: + case AuthenticationType::BCRYPT_PASSWORD: case AuthenticationType::LDAP: throw Authentication::Require("ClickHouse Basic Authentication"); @@ -109,6 +115,7 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const return checkPasswordDoubleSHA1MySQL(mysql_credentials->getScramble(), mysql_credentials->getScrambledPassword(), auth_data.getPasswordHashBinary()); case AuthenticationType::SHA256_PASSWORD: + case AuthenticationType::BCRYPT_PASSWORD: case AuthenticationType::LDAP: case AuthenticationType::KERBEROS: throw Authentication::Require("ClickHouse Basic Authentication"); @@ -137,6 +144,9 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const case AuthenticationType::DOUBLE_SHA1_PASSWORD: return checkPasswordDoubleSHA1(basic_credentials->getPassword(), auth_data.getPasswordHashBinary()); + case AuthenticationType::BCRYPT_PASSWORD: + return checkPasswordBcrypt(basic_credentials->getPassword(), auth_data.getPasswordHashBinary()); + case AuthenticationType::LDAP: return external_authenticators.checkLDAPCredentials(auth_data.getLDAPServerName(), *basic_credentials); @@ -159,6 +169,7 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const case AuthenticationType::PLAINTEXT_PASSWORD: case AuthenticationType::SHA256_PASSWORD: case AuthenticationType::DOUBLE_SHA1_PASSWORD: + case AuthenticationType::BCRYPT_PASSWORD: case AuthenticationType::LDAP: throw Authentication::Require("ClickHouse Basic Authentication"); diff --git a/src/Access/Common/AuthenticationData.cpp b/src/Access/Common/AuthenticationData.cpp index f3d3bb5b758..e3e48e92422 100644 --- a/src/Access/Common/AuthenticationData.cpp +++ b/src/Access/Common/AuthenticationData.cpp @@ -6,6 +6,11 @@ #include #include +#include "config.h" + +#if USE_BCRYPT +# include +#endif namespace DB { @@ -49,6 +54,11 @@ const AuthenticationTypeInfo & AuthenticationTypeInfo::get(AuthenticationType ty static const auto info = make_info("DOUBLE_SHA1_PASSWORD"); return info; } + case AuthenticationType::BCRYPT_PASSWORD: + { + static const auto info = make_info("BCRYPT_PASSWORD"); + return info; + } case AuthenticationType::LDAP: { static const auto info = make_info("LDAP"); @@ -85,6 +95,40 @@ AuthenticationData::Digest AuthenticationData::Util::encodeSHA256(std::string_vi #endif } +AuthenticationData::Digest AuthenticationData::Util::encodeBcrypt(std::string_view text [[maybe_unused]]) +{ +#if USE_BCRYPT + char salt[BCRYPT_HASHSIZE]; + Digest hash; + hash.resize(64); + int ret = bcrypt_gensalt(12, salt); + if (ret != 0) + throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "BCrypt library failed: bcrypt_gensalt returned {}", ret); + ret = bcrypt_hashpw(text.data(), salt, reinterpret_cast(hash.data())); + if (ret != 0) + throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "BCrypt library failed: bcrypt_hashpw returned {}", ret); + return hash; +#else + throw DB::Exception( + "bcrypt passwords support is disabled, because ClickHouse was built without bcrypt library", + DB::ErrorCodes::SUPPORT_IS_DISABLED); +#endif +} + +bool AuthenticationData::Util::checkPasswordBcrypt(std::string_view password [[maybe_unused]], const Digest & password_bcrypt [[maybe_unused]]) +{ +#if USE_BCRYPT + int ret = bcrypt_checkpw(password.data(), reinterpret_cast(password_bcrypt.data())); + if (ret == -1) + throw DB::Exception(DB::ErrorCodes::LOGICAL_ERROR, "BCrypt library failed: bcrypt_checkpw returned {}", ret); + return (ret == 0); +#else + throw DB::Exception( + "bcrypt passwords support is disabled, because ClickHouse was built without bcrypt library", + DB::ErrorCodes::SUPPORT_IS_DISABLED); +#endif +} + AuthenticationData::Digest AuthenticationData::Util::encodeSHA1(std::string_view text) { @@ -115,6 +159,9 @@ void AuthenticationData::setPassword(const String & password_) case AuthenticationType::DOUBLE_SHA1_PASSWORD: return setPasswordHashBinary(Util::encodeDoubleSHA1(password_)); + case AuthenticationType::BCRYPT_PASSWORD: + return setPasswordHashBinary(Util::encodeBcrypt(password_)); + case AuthenticationType::NO_PASSWORD: case AuthenticationType::LDAP: case AuthenticationType::KERBEROS: @@ -198,6 +245,12 @@ void AuthenticationData::setPasswordHashBinary(const Digest & hash) return; } + case AuthenticationType::BCRYPT_PASSWORD: + { + password_hash = hash; + return; + } + case AuthenticationType::NO_PASSWORD: case AuthenticationType::LDAP: case AuthenticationType::KERBEROS: diff --git a/src/Access/Common/AuthenticationData.h b/src/Access/Common/AuthenticationData.h index ced9fcd4b6d..4f13b953684 100644 --- a/src/Access/Common/AuthenticationData.h +++ b/src/Access/Common/AuthenticationData.h @@ -22,6 +22,9 @@ enum class AuthenticationType /// This kind of hash is used by the `mysql_native_password` authentication plugin. DOUBLE_SHA1_PASSWORD, + /// Password is encrypted in bcrypt hash. + BCRYPT_PASSWORD, + /// Password is checked by a [remote] LDAP server. Connection will be made at each authentication attempt. LDAP, @@ -102,6 +105,8 @@ public: static Digest encodeSHA1(const Digest & text) { return encodeSHA1(std::string_view{reinterpret_cast(text.data()), text.size()}); } static Digest encodeDoubleSHA1(std::string_view text) { return encodeSHA1(encodeSHA1(text)); } static Digest encodeDoubleSHA1(const Digest & text) { return encodeSHA1(encodeSHA1(text)); } + static Digest encodeBcrypt(std::string_view text); + static bool checkPasswordBcrypt(std::string_view password, const Digest & password_bcrypt); }; private: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e8fe368dfa..7d670cd6e87 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -536,6 +536,10 @@ if (TARGET ch_contrib::sqlite) dbms_target_link_libraries(PUBLIC ch_contrib::sqlite) endif() +if (TARGET ch_contrib::bcrypt) + target_link_libraries (clickhouse_common_io PUBLIC ch_contrib::bcrypt) +endif() + if (TARGET ch_contrib::msgpack) target_link_libraries (clickhouse_common_io PUBLIC ch_contrib::msgpack) endif() diff --git a/src/Common/config.h.in b/src/Common/config.h.in index baa480a6545..9444f64492b 100644 --- a/src/Common/config.h.in +++ b/src/Common/config.h.in @@ -54,3 +54,4 @@ #cmakedefine01 USE_BLAKE3 #cmakedefine01 USE_SKIM #cmakedefine01 USE_OPENSSL_INTREE +#cmakedefine01 USE_BCRYPT diff --git a/src/Interpreters/SessionLog.cpp b/src/Interpreters/SessionLog.cpp index 79aac63b40c..5c7654db29c 100644 --- a/src/Interpreters/SessionLog.cpp +++ b/src/Interpreters/SessionLog.cpp @@ -84,12 +84,13 @@ NamesAndTypesList SessionLogElement::getNamesAndTypes() AUTH_TYPE_NAME_AND_VALUE(AuthType::PLAINTEXT_PASSWORD), AUTH_TYPE_NAME_AND_VALUE(AuthType::SHA256_PASSWORD), AUTH_TYPE_NAME_AND_VALUE(AuthType::DOUBLE_SHA1_PASSWORD), + AUTH_TYPE_NAME_AND_VALUE(AuthType::BCRYPT_PASSWORD), AUTH_TYPE_NAME_AND_VALUE(AuthType::LDAP), AUTH_TYPE_NAME_AND_VALUE(AuthType::KERBEROS), AUTH_TYPE_NAME_AND_VALUE(AuthType::SSL_CERTIFICATE), }); #undef AUTH_TYPE_NAME_AND_VALUE - static_assert(static_cast(AuthenticationType::MAX) == 7); + static_assert(static_cast(AuthenticationType::MAX) == 8); auto interface_type_column = std::make_shared( DataTypeEnum8::Values diff --git a/src/Parsers/Access/ASTCreateUserQuery.cpp b/src/Parsers/Access/ASTCreateUserQuery.cpp index a59b5dd472c..ef62a0bf2bd 100644 --- a/src/Parsers/Access/ASTCreateUserQuery.cpp +++ b/src/Parsers/Access/ASTCreateUserQuery.cpp @@ -64,6 +64,13 @@ namespace password = auth_data.getPasswordHashHex(); break; } + case AuthenticationType::BCRYPT_PASSWORD: + { + auth_type_name = "bcrypt_hash"; + prefix = "BY"; + password = auth_data.getPasswordHashHex(); + break; + } case AuthenticationType::LDAP: { prefix = "SERVER"; diff --git a/src/Parsers/Access/ParserCreateUserQuery.cpp b/src/Parsers/Access/ParserCreateUserQuery.cpp index de83c5760c1..04e7940f970 100644 --- a/src/Parsers/Access/ParserCreateUserQuery.cpp +++ b/src/Parsers/Access/ParserCreateUserQuery.cpp @@ -104,6 +104,11 @@ namespace type = AuthenticationType::DOUBLE_SHA1_PASSWORD; expect_hash = true; } + else if (ParserKeyword{"BCRYPT_HASH"}.ignore(pos, expected)) + { + type = AuthenticationType::BCRYPT_PASSWORD; + expect_hash = true; + } else return false; } diff --git a/src/configure_config.cmake b/src/configure_config.cmake index 58cb34b7d67..ddeb990e6f7 100644 --- a/src/configure_config.cmake +++ b/src/configure_config.cmake @@ -138,6 +138,9 @@ endif() if (TARGET ch_contrib::capnp) set(USE_CAPNP 1) endif() +if (TARGET ch_contrib::bcrypt) + set(USE_BCRYPT 1) +endif() if (NOT (ENABLE_OPENSSL OR ENABLE_OPENSSL_DYNAMIC)) set(USE_BORINGSSL 1) endif ()