1#include <Access/Authentication.h>
2#include <Common/Exception.h>
3#include <common/StringRef.h>
4#include <Core/Defines.h>
5#include <Poco/SHA1Engine.h>
6#include <boost/algorithm/hex.hpp>
7#include "config_core.h"
8#if USE_SSL
9# include <openssl/sha.h>
10#endif
11
12
13namespace DB
14{
15namespace ErrorCodes
16{
17 extern const int SUPPORT_IS_DISABLED;
18 extern const int REQUIRED_PASSWORD;
19 extern const int WRONG_PASSWORD;
20 extern const int BAD_ARGUMENTS;
21 extern const int LOGICAL_ERROR;
22}
23
24
25namespace
26{
27 using Digest = Authentication::Digest;
28
29 Digest encodePlainText(const StringRef & text)
30 {
31 return Digest(text.data, text.data + text.size);
32 }
33
34 Digest encodeSHA256(const StringRef & text)
35 {
36#if USE_SSL
37 Digest hash;
38 hash.resize(32);
39 SHA256_CTX ctx;
40 SHA256_Init(&ctx);
41 SHA256_Update(&ctx, reinterpret_cast<const UInt8 *>(text.data), text.size);
42 SHA256_Final(hash.data(), &ctx);
43 return hash;
44#else
45 UNUSED(text);
46 throw DB::Exception("SHA256 passwords support is disabled, because ClickHouse was built without SSL library", DB::ErrorCodes::SUPPORT_IS_DISABLED);
47#endif
48 }
49
50 Digest encodeSHA1(const StringRef & text)
51 {
52 Poco::SHA1Engine engine;
53 engine.update(text.data, text.size);
54 return engine.digest();
55 }
56
57 Digest encodeSHA1(const Digest & text)
58 {
59 return encodeSHA1(StringRef{reinterpret_cast<const char *>(text.data()), text.size()});
60 }
61
62 Digest encodeDoubleSHA1(const StringRef & text)
63 {
64 return encodeSHA1(encodeSHA1(text));
65 }
66}
67
68
69Authentication::Authentication(Authentication::Type type_)
70 : type(type_)
71{
72}
73
74
75void Authentication::setPassword(const String & password_)
76{
77 switch (type)
78 {
79 case NO_PASSWORD:
80 throw Exception("Cannot specify password for the 'NO_PASSWORD' authentication type", ErrorCodes::LOGICAL_ERROR);
81
82 case PLAINTEXT_PASSWORD:
83 setPasswordHashBinary(encodePlainText(password_));
84 return;
85
86 case SHA256_PASSWORD:
87 setPasswordHashBinary(encodeSHA256(password_));
88 return;
89
90 case DOUBLE_SHA1_PASSWORD:
91 setPasswordHashBinary(encodeDoubleSHA1(password_));
92 return;
93 }
94 throw Exception("Unknown authentication type: " + std::to_string(static_cast<int>(type)), ErrorCodes::LOGICAL_ERROR);
95}
96
97
98String Authentication::getPassword() const
99{
100 if (type != PLAINTEXT_PASSWORD)
101 throw Exception("Cannot decode the password", ErrorCodes::LOGICAL_ERROR);
102 return String(password_hash.data(), password_hash.data() + password_hash.size());
103}
104
105
106void Authentication::setPasswordHashHex(const String & hash)
107{
108 Digest digest;
109 digest.resize(hash.size() / 2);
110 boost::algorithm::unhex(hash.begin(), hash.end(), digest.data());
111 setPasswordHashBinary(digest);
112}
113
114
115String Authentication::getPasswordHashHex() const
116{
117 String hex;
118 hex.resize(password_hash.size() * 2);
119 boost::algorithm::hex(password_hash.begin(), password_hash.end(), hex.data());
120 return hex;
121}
122
123
124void Authentication::setPasswordHashBinary(const Digest & hash)
125{
126 switch (type)
127 {
128 case NO_PASSWORD:
129 throw Exception("Cannot specify password for the 'NO_PASSWORD' authentication type", ErrorCodes::LOGICAL_ERROR);
130
131 case PLAINTEXT_PASSWORD:
132 {
133 password_hash = hash;
134 return;
135 }
136
137 case SHA256_PASSWORD:
138 {
139 if (hash.size() != 32)
140 throw Exception(
141 "Password hash for the 'SHA256_PASSWORD' authentication type has length " + std::to_string(hash.size())
142 + " but must be exactly 32 bytes.",
143 ErrorCodes::BAD_ARGUMENTS);
144 password_hash = hash;
145 return;
146 }
147
148 case DOUBLE_SHA1_PASSWORD:
149 {
150 if (hash.size() != 20)
151 throw Exception(
152 "Password hash for the 'DOUBLE_SHA1_PASSWORD' authentication type has length " + std::to_string(hash.size())
153 + " but must be exactly 20 bytes.",
154 ErrorCodes::BAD_ARGUMENTS);
155 password_hash = hash;
156 return;
157 }
158 }
159 throw Exception("Unknown authentication type: " + std::to_string(static_cast<int>(type)), ErrorCodes::LOGICAL_ERROR);
160}
161
162
163Digest Authentication::getPasswordDoubleSHA1() const
164{
165 switch (type)
166 {
167 case NO_PASSWORD:
168 {
169 Poco::SHA1Engine engine;
170 return engine.digest();
171 }
172
173 case PLAINTEXT_PASSWORD:
174 {
175 Poco::SHA1Engine engine;
176 engine.update(getPassword());
177 const Digest & first_sha1 = engine.digest();
178 engine.update(first_sha1.data(), first_sha1.size());
179 return engine.digest();
180 }
181
182 case SHA256_PASSWORD:
183 throw Exception("Cannot get password double SHA1 for user with 'SHA256_PASSWORD' authentication.", ErrorCodes::BAD_ARGUMENTS);
184
185 case DOUBLE_SHA1_PASSWORD:
186 return password_hash;
187 }
188 throw Exception("Unknown authentication type: " + std::to_string(static_cast<int>(type)), ErrorCodes::LOGICAL_ERROR);
189}
190
191
192bool Authentication::isCorrectPassword(const String & password_) const
193{
194 switch (type)
195 {
196 case NO_PASSWORD:
197 return true;
198
199 case PLAINTEXT_PASSWORD:
200 {
201 if (password_ == StringRef{reinterpret_cast<const char *>(password_hash.data()), password_hash.size()})
202 return true;
203
204 // For compatibility with MySQL clients which support only native authentication plugin, SHA1 can be passed instead of password.
205 auto password_sha1 = encodeSHA1(password_hash);
206 return password_ == StringRef{reinterpret_cast<const char *>(password_sha1.data()), password_sha1.size()};
207 }
208
209 case SHA256_PASSWORD:
210 return encodeSHA256(password_) == password_hash;
211
212 case DOUBLE_SHA1_PASSWORD:
213 {
214 auto first_sha1 = encodeSHA1(password_);
215
216 /// If it was MySQL compatibility server, then first_sha1 already contains double SHA1.
217 if (first_sha1 == password_hash)
218 return true;
219
220 return encodeSHA1(first_sha1) == password_hash;
221 }
222 }
223 throw Exception("Unknown authentication type: " + std::to_string(static_cast<int>(type)), ErrorCodes::LOGICAL_ERROR);
224}
225
226
227void Authentication::checkPassword(const String & password_, const String & user_name) const
228{
229 if (isCorrectPassword(password_))
230 return;
231 auto info_about_user_name = [&user_name]() { return user_name.empty() ? String() : " for user " + user_name; };
232 if (password_.empty() && (type != NO_PASSWORD))
233 throw Exception("Password required" + info_about_user_name(), ErrorCodes::REQUIRED_PASSWORD);
234 throw Exception("Wrong password" + info_about_user_name(), ErrorCodes::WRONG_PASSWORD);
235}
236
237
238bool operator ==(const Authentication & lhs, const Authentication & rhs)
239{
240 return (lhs.type == rhs.type) && (lhs.password_hash == rhs.password_hash);
241}
242}
243
244