Merge pull request #36172 from DevTeamBK/feature-password

password hash salt feature
This commit is contained in:
Yakov Olkhovskiy 2022-04-20 17:52:25 -04:00 committed by GitHub
commit a7318b3a96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 57 deletions

View File

@ -16,18 +16,6 @@
#include <Interpreters/Access/InterpreterGrantQuery.h>
#include <Interpreters/Access/InterpreterShowCreateAccessEntityQuery.h>
#include <Interpreters/Access/InterpreterShowGrantsQuery.h>
#include <Parsers/Access/ASTCreateQuotaQuery.h>
#include <Parsers/Access/ASTCreateRoleQuery.h>
#include <Parsers/Access/ASTCreateRowPolicyQuery.h>
#include <Parsers/Access/ASTCreateSettingsProfileQuery.h>
#include <Parsers/Access/ASTCreateUserQuery.h>
#include <Parsers/Access/ASTGrantQuery.h>
#include <Parsers/Access/ParserCreateQuotaQuery.h>
#include <Parsers/Access/ParserCreateRoleQuery.h>
#include <Parsers/Access/ParserCreateRowPolicyQuery.h>
#include <Parsers/Access/ParserCreateSettingsProfileQuery.h>
#include <Parsers/Access/ParserCreateUserQuery.h>
#include <Parsers/Access/ParserGrantQuery.h>
#include <Parsers/formatAST.h>
#include <Parsers/parseQuery.h>
#include <boost/range/algorithm/copy.hpp>
@ -40,39 +28,6 @@ namespace ErrorCodes
extern const int INCORRECT_ACCESS_ENTITY_DEFINITION;
}
namespace
{
/// Special parser for the 'ATTACH access entity' queries.
class ParserAttachAccessEntity : public IParserBase
{
protected:
const char * getName() const override { return "ATTACH access entity query"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override
{
ParserCreateUserQuery create_user_p;
ParserCreateRoleQuery create_role_p;
ParserCreateRowPolicyQuery create_policy_p;
ParserCreateQuotaQuery create_quota_p;
ParserCreateSettingsProfileQuery create_profile_p;
ParserGrantQuery grant_p;
create_user_p.useAttachMode();
create_role_p.useAttachMode();
create_policy_p.useAttachMode();
create_quota_p.useAttachMode();
create_profile_p.useAttachMode();
grant_p.useAttachMode();
return create_user_p.parse(pos, node, expected) || create_role_p.parse(pos, node, expected)
|| create_policy_p.parse(pos, node, expected) || create_quota_p.parse(pos, node, expected)
|| create_profile_p.parse(pos, node, expected) || grant_p.parse(pos, node, expected);
}
};
}
String serializeAccessEntity(const IAccessEntity & entity)
{
/// Build list of ATTACH queries.

View File

@ -1,10 +1,51 @@
#pragma once
#include <Parsers/Access/ASTCreateQuotaQuery.h>
#include <Parsers/Access/ASTCreateRoleQuery.h>
#include <Parsers/Access/ASTCreateRowPolicyQuery.h>
#include <Parsers/Access/ASTCreateSettingsProfileQuery.h>
#include <Parsers/Access/ASTCreateUserQuery.h>
#include <Parsers/Access/ASTGrantQuery.h>
#include <Parsers/Access/ParserCreateQuotaQuery.h>
#include <Parsers/Access/ParserCreateRoleQuery.h>
#include <Parsers/Access/ParserCreateRowPolicyQuery.h>
#include <Parsers/Access/ParserCreateSettingsProfileQuery.h>
#include <Parsers/Access/ParserCreateUserQuery.h>
#include <Parsers/Access/ParserGrantQuery.h>
#include <base/types.h>
#include <memory>
namespace DB
{
/// Special parser for the 'ATTACH access entity' queries.
class ParserAttachAccessEntity : public IParserBase
{
protected:
const char * getName() const override { return "ATTACH access entity query"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override
{
ParserCreateUserQuery create_user_p;
ParserCreateRoleQuery create_role_p;
ParserCreateRowPolicyQuery create_policy_p;
ParserCreateQuotaQuery create_quota_p;
ParserCreateSettingsProfileQuery create_profile_p;
ParserGrantQuery grant_p;
create_user_p.useAttachMode();
create_role_p.useAttachMode();
create_policy_p.useAttachMode();
create_quota_p.useAttachMode();
create_profile_p.useAttachMode();
grant_p.useAttachMode();
return create_user_p.parse(pos, node, expected) || create_role_p.parse(pos, node, expected)
|| create_policy_p.parse(pos, node, expected) || create_quota_p.parse(pos, node, expected)
|| create_profile_p.parse(pos, node, expected) || grant_p.parse(pos, node, expected);
}
};
struct IAccessEntity;
using AccessEntityPtr = std::shared_ptr<const IAccessEntity>;

View File

@ -31,9 +31,9 @@ namespace
return (Util::encodeDoubleSHA1(password) == password_double_sha1);
}
bool checkPasswordSHA256(const std::string_view & password, const Digest & password_sha256)
bool checkPasswordSHA256(const std::string_view & password, const Digest & password_sha256, const String & salt)
{
return Util::encodeSHA256(password) == password_sha256;
return Util::encodeSHA256(String(password).append(salt)) == password_sha256;
}
bool checkPasswordDoubleSHA1MySQL(const std::string_view & scramble, const std::string_view & scrambled_password, const Digest & password_double_sha1)
@ -132,7 +132,7 @@ bool Authentication::areCredentialsValid(const Credentials & credentials, const
return checkPasswordPlainText(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
case AuthenticationType::SHA256_PASSWORD:
return checkPasswordSHA256(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());
return checkPasswordSHA256(basic_credentials->getPassword(), auth_data.getPasswordHashBinary(), auth_data.getSalt());
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
return checkPasswordDoubleSHA1(basic_credentials->getPassword(), auth_data.getPasswordHashBinary());

View File

@ -210,6 +210,17 @@ void AuthenticationData::setPasswordHashBinary(const Digest & hash)
throw Exception("setPasswordHashBinary(): authentication type " + toString(type) + " not supported", ErrorCodes::NOT_IMPLEMENTED);
}
void AuthenticationData::setSalt(String salt_)
{
if (type != AuthenticationType::SHA256_PASSWORD)
throw Exception("setSalt(): authentication type " + toString(type) + " not supported", ErrorCodes::NOT_IMPLEMENTED);
salt = std::move(salt_);
}
String AuthenticationData::getSalt() const
{
return salt;
}
void AuthenticationData::setSSLCertificateCommonNames(boost::container::flat_set<String> common_names_)
{

View File

@ -76,6 +76,10 @@ public:
void setPasswordHashBinary(const Digest & hash);
const Digest & getPasswordHashBinary() const { return password_hash; }
/// Sets the salt in String form.
void setSalt(String salt);
String getSalt() const;
/// Sets the server name for authentication type LDAP.
const String & getLDAPServerName() const { return ldap_server_name; }
void setLDAPServerName(const String & name) { ldap_server_name = name; }
@ -106,6 +110,7 @@ private:
String ldap_server_name;
String kerberos_realm;
boost::container::flat_set<String> ssl_certificate_common_names;
String salt;
};
}

View File

@ -36,6 +36,7 @@ namespace
String auth_type_name = AuthenticationTypeInfo::get(auth_type).name;
String value_prefix;
std::optional<String> value;
std::optional<String> salt;
const boost::container::flat_set<String> * values = nullptr;
if (show_password ||
@ -56,6 +57,10 @@ namespace
auth_type_name = "sha256_hash";
value_prefix = "BY";
value = auth_data.getPasswordHashHex();
if (!auth_data.getSalt().empty())
{
salt = auth_data.getSalt();
}
break;
}
case AuthenticationType::DOUBLE_SHA1_PASSWORD:
@ -107,6 +112,8 @@ namespace
if (value)
{
settings.ostr << " " << quoteString(*value);
if (salt)
settings.ostr << " SALT " << quoteString(*salt);
}
else if (values)
{

View File

@ -16,7 +16,12 @@
#include <base/range.h>
#include <boost/algorithm/string/predicate.hpp>
#include <base/insertAtEnd.h>
#include <Common/config.h>
#include <Common/hex.h>
#if USE_SSL
# include <openssl/crypto.h>
# include <openssl/rand.h>
#endif
namespace DB
{
@ -34,7 +39,7 @@ namespace
}
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, AuthenticationData & auth_data)
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, bool id_mode, AuthenticationData & auth_data)
{
return IParserBase::wrapParseImpl(pos, [&]
{
@ -99,14 +104,22 @@ namespace
}
String value;
String parsed_salt;
boost::container::flat_set<String> common_names;
if (expect_password || expect_hash)
{
ASTPtr ast;
if (!ParserKeyword{"BY"}.ignore(pos, expected) || !ParserStringLiteral{}.parse(pos, ast, expected))
return false;
value = ast->as<const ASTLiteral &>().value.safeGet<String>();
if (id_mode && expect_hash)
{
if (ParserKeyword{"SALT"}.ignore(pos, expected) && ParserStringLiteral{}.parse(pos, ast, expected))
{
parsed_salt = ast->as<const ASTLiteral &>().value.safeGet<String>();
}
}
}
else if (expect_ldap_server_name)
{
@ -141,6 +154,34 @@ namespace
}
auth_data = AuthenticationData{*type};
if (auth_data.getType() == AuthenticationType::SHA256_PASSWORD)
{
if (!parsed_salt.empty())
{
auth_data.setSalt(parsed_salt);
}
else if (expect_password)
{
#if USE_SSL
///generate and add salt here
///random generator FIPS complaint
uint8_t key[32];
RAND_bytes(key, sizeof(key));
String salt;
salt.resize(sizeof(key) * 2);
char * buf_pos = salt.data();
for (uint8_t k : key)
{
writeHexByteUppercase(k, buf_pos);
buf_pos += 2;
}
value.append(salt);
auth_data.setSalt(salt);
#else
///if USE_SSL is not defined, Exception thrown later
#endif
}
}
if (expect_password)
auth_data.setPassword(value);
else if (expect_hash)
@ -393,7 +434,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (!auth_data)
{
AuthenticationData new_auth_data;
if (parseAuthenticationData(pos, expected, new_auth_data))
if (parseAuthenticationData(pos, expected, attach_mode, new_auth_data))
{
auth_data = std::move(new_auth_data);
continue;

View File

@ -9,9 +9,9 @@
#include <Parsers/ParserQueryWithOutput.h>
#include <Parsers/formatAST.h>
#include <Parsers/parseQuery.h>
#include "Access/AccessEntityIO.h"
#include <string_view>
#include <regex>
#include <gtest/gtest.h>
namespace
@ -20,6 +20,7 @@ using namespace DB;
using namespace std::literals;
}
struct ParserTestCase
{
const std::string_view input_text;
@ -47,12 +48,35 @@ TEST_P(ParserTest, parseQuery)
ASSERT_NE(nullptr, parser);
if (expected_ast)
{
if (std::string(expected_ast).starts_with("throws"))
{
EXPECT_THROW(parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0), DB::Exception);
}
else
{
ASTPtr ast;
ASSERT_NO_THROW(ast = parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0));
if (std::string("CREATE USER or ALTER USER query") != parser->getName()
&& std::string("ATTACH access entity query") != parser->getName())
{
EXPECT_EQ(expected_ast, serializeAST(*ast->clone(), false));
}
else
{
if (input_text.starts_with("ATTACH"))
{
auto salt = (dynamic_cast<const ASTCreateUserQuery *>(ast.get())->auth_data)->getSalt();
EXPECT_TRUE(std::regex_match(salt, std::regex(expected_ast)));
}
else
{
EXPECT_TRUE(std::regex_match(serializeAST(*ast->clone(), false), std::regex(expected_ast)));
}
}
}
}
else
{
ASSERT_THROW(parseQuery(*parser, input_text.begin(), input_text.end(), 0, 0), DB::Exception);
}
@ -226,3 +250,35 @@ INSTANTIATE_TEST_SUITE_P(ParserCreateDatabaseQuery, ParserTest,
"CREATE DATABASE db\nENGINE = Foo\nSETTINGS a = 1, b = 2\nTABLE OVERRIDE `a`\n(\n ORDER BY (`id`, `version`)\n)\nCOMMENT 'db comment'"
}
})));
INSTANTIATE_TEST_SUITE_P(ParserCreateUserQuery, ParserTest,
::testing::Combine(
::testing::Values(std::make_shared<ParserCreateUserQuery>()),
::testing::ValuesIn(std::initializer_list<ParserTestCase>{
{
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
"CREATE USER user1 IDENTIFIED WITH sha256_hash BY '[A-Za-z0-9]{64}' SALT '[A-Za-z0-9]{64}'"
},
{
"ALTER USER user1 IDENTIFIED WITH sha256_password BY 'qwe123'",
"ALTER USER user1 IDENTIFIED WITH sha256_hash BY '[A-Za-z0-9]{64}' SALT '[A-Za-z0-9]{64}'"
},
{
"CREATE USER user1 IDENTIFIED WITH sha256_password BY 'qwe123' SALT 'EFFD7F6B03B3EA68B8F86C1E91614DD50E42EB31EF7160524916444D58B5E264'",
"throws Syntax error"
}
})));
INSTANTIATE_TEST_SUITE_P(ParserAttachUserQuery, ParserTest,
::testing::Combine(
::testing::Values(std::make_shared<ParserAttachAccessEntity>()),
::testing::ValuesIn(std::initializer_list<ParserTestCase>{
{
"ATTACH USER user1 IDENTIFIED WITH sha256_hash BY '2CC4880302693485717D34E06046594CFDFE425E3F04AA5A094C4AABAB3CB0BF' SALT 'EFFD7F6B03B3EA68B8F86C1E91614DD50E42EB31EF7160524916444D58B5E264';",
"^[A-Za-z0-9]{64}$"
},
{
"ATTACH USER user1 IDENTIFIED WITH sha256_hash BY '2CC4880302693485717D34E06046594CFDFE425E3F04AA5A094C4AABAB3CB0BF'", //for users created in older releases that sha256_password has no salt
"^$"
}
})));