Cutelyst  2.5.0
credentialpassword.cpp
1 /*
2  * Copyright (C) 2013-2018 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "credentialpassword_p.h"
19 #include "authenticationrealm.h"
20 
21 #include <QLoggingCategory>
22 #include <QMessageAuthenticationCode>
23 #include <QUuid>
24 #include <QFile>
25 
26 using namespace Cutelyst;
27 
28 Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
29 
31  , d_ptr(new CredentialPasswordPrivate)
32 {
33 
34 }
35 
36 CredentialPassword::~CredentialPassword()
37 {
38  delete d_ptr;
39 }
40 
42 {
43  AuthenticationUser user;
44  Q_D(CredentialPassword);
45  AuthenticationUser _user = realm->findUser(c, authinfo);
46  if (!_user.isNull()) {
47  if (d->checkPassword(_user, authinfo)) {
48  user = _user;
49  } else {
50  qCDebug(C_CREDENTIALPASSWORD) << "Password didn't match";
51  }
52  } else {
53  qCDebug(C_CREDENTIALPASSWORD) << "Unable to locate a user matching user info provided in realm";
54  }
55  return user;
56 }
57 
59 {
60  Q_D(const CredentialPassword);
61  return d->passwordField;
62 }
63 
64 void CredentialPassword::setPasswordField(const QString &fieldName)
65 {
66  Q_D(CredentialPassword);
67  d->passwordField = fieldName;
68 }
69 
70 CredentialPassword::PasswordType CredentialPassword::passwordType() const
71 {
72  Q_D(const CredentialPassword);
73  return d->passwordType;
74 }
75 
76 void CredentialPassword::setPasswordType(Cutelyst::CredentialPassword::PasswordType type)
77 {
78  Q_D(CredentialPassword);
79  d->passwordType = type;
80 }
81 
83 {
84  Q_D(const CredentialPassword);
85  return d->passwordPreSalt;
86 }
87 
89 {
90  Q_D(CredentialPassword);
91  d->passwordPreSalt = passwordPreSalt;
92 }
93 
95 {
96  Q_D(const CredentialPassword);
97  return d->passwordPostSalt;
98 }
99 
101 {
102  Q_D(CredentialPassword);
103  d->passwordPostSalt = passwordPostSalt;
104 }
105 
106 // To avoid timming attack
107 bool slowEquals(const QByteArray &a, const QByteArray &b)
108 {
109  int diff = a.size() ^ b.size();
110  for(int i = 0; i < a.size() && i < b.size(); i++) {
111  diff |= a[i] ^ b[i];
112  }
113  return diff == 0;
114 }
115 
116 #define HASH_SECTIONS 4
117 #define HASH_ALGORITHM_INDEX 0
118 #define HASH_ITERATION_INDEX 1
119 #define HASH_SALT_INDEX 2
120 #define HASH_PBKDF2_INDEX 3
121 bool CredentialPassword::validatePassword(const QByteArray &password, const QByteArray &correctHash)
122 {
123  QByteArrayList params = correctHash.split(':');
124  if (params.size() < HASH_SECTIONS) {
125  return false;
126  }
127 
128  int method = CredentialPasswordPrivate::cryptoStrToEnum(params.at(HASH_ALGORITHM_INDEX));
129  if (method == -1) {
130  return false;
131  }
132 
133  QByteArray pbkdf2Hash = QByteArray::fromBase64(params.at(HASH_PBKDF2_INDEX));
134  return slowEquals(
135  pbkdf2Hash,
136  pbkdf2(
137  static_cast<QCryptographicHash::Algorithm>(method),
138  password,
139  params.at(HASH_SALT_INDEX),
140  params.at(HASH_ITERATION_INDEX).toInt(),
141  pbkdf2Hash.length()
142  )
143  );
144 }
145 
146 QByteArray CredentialPassword::createPassword(const QByteArray &password, QCryptographicHash::Algorithm method, int iterations, int saltByteSize, int hashByteSize)
147 {
148  QByteArray salt;
149 #ifdef Q_OS_LINUX
150  QFile random(QStringLiteral("/dev/urandom"));
151  if (random.open(QIODevice::ReadOnly)) {
152  salt = random.read(saltByteSize).toBase64();
153  } else {
154 #endif
155  salt = QUuid::createUuid().toRfc4122().toBase64();
156 #ifdef Q_OS_LINUX
157  }
158 #endif
159 
160  const QByteArray methodStr = CredentialPasswordPrivate::cryptoEnumToStr(method);
161  return methodStr + ':' + QByteArray::number(iterations) + ':' + salt + ':' +
162  pbkdf2(
163  method,
164  password,
165  salt,
166  iterations,
167  hashByteSize
168  ).toBase64();
169 }
170 
171 QByteArray CredentialPassword::createPassword(const QByteArray &password)
172 {
173  return createPassword(password, QCryptographicHash::Sha512, 10000, 16, 16);
174 }
175 
176 // TODO https://crackstation.net/hashing-security.htm
177 // shows a different Algorithm that seems a bit simpler
178 // this one does passes the RFC6070 tests
179 // https://www.ietf.org/rfc/rfc6070.txt
180 QByteArray CredentialPassword::pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
181 {
182  QByteArray key;
183 
184  if (rounds <= 0 || keyLength <= 0) {
185  qCCritical(C_CREDENTIALPASSWORD, "PBKDF2 ERROR: Invalid parameters.");
186  return key;
187  }
188 
189  if (salt.size() == 0 || salt.size() > std::numeric_limits<int>::max() - 4) {
190  return key;
191  }
192  key.reserve(keyLength);
193 
194  int saltSize = salt.size();
195  QByteArray asalt = salt;
196  asalt.resize(saltSize + 4);
197 
198  QByteArray d1, obuf;
199 
200  QMessageAuthenticationCode code(method, password);
201 
202  for (int count = 1, remainingBytes = keyLength; remainingBytes > 0; ++count) {
203  asalt[saltSize + 0] = static_cast<char>((count >> 24) & 0xff);
204  asalt[saltSize + 1] = static_cast<char>((count >> 16) & 0xff);
205  asalt[saltSize + 2] = static_cast<char>((count >> 8) & 0xff);
206  asalt[saltSize + 3] = static_cast<char>(count & 0xff);
207 
208  code.reset();
209  code.addData(asalt);
210  obuf = d1 = code.result();
211 
212  for (int i = 1; i < rounds; ++i) {
213  code.reset();
214  code.addData(d1);
215  d1 = code.result();
216  auto it = obuf.begin();
217  auto d1It = d1.cbegin();
218  while (d1It != d1.cend()) {
219  *it = *it ^ *d1It;
220  ++it;
221  ++d1It;
222  }
223  }
224 
225  key.append(obuf);
226  remainingBytes -= obuf.size();
227  }
228 
229  key = key.mid(0, keyLength);
230  return key;
231 }
232 
233 QByteArray CredentialPassword::hmac(QCryptographicHash::Algorithm method, QByteArray key, const QByteArray &message)
234 {
235  QByteArray ret;
236  const int blocksize = 64;
237  if (key.length() > blocksize) {
238  ret = QCryptographicHash::hash(key, method);
239  return ret;
240  }
241 
242  while (key.length() < blocksize) {
243  key.append('\0');
244  }
245 
246  QByteArray o_key_pad('\x5c', blocksize);
247  o_key_pad.fill('\x5c', blocksize);
248 
249  QByteArray i_key_pad;
250  i_key_pad.fill('\x36', blocksize);
251 
252  for (int i=0; i < blocksize; i++) {
253  o_key_pad[i] = o_key_pad[i] ^ key[i];
254  i_key_pad[i] = i_key_pad[i] ^ key[i];
255  }
256 
257  ret = QCryptographicHash::hash(o_key_pad + QCryptographicHash::hash(i_key_pad + message, method),
258  method);
259  return ret;
260 }
261 
262 bool CredentialPasswordPrivate::checkPassword(const AuthenticationUser &user, const ParamsMultiMap &authinfo)
263 {
264  QString password = authinfo.value(passwordField);
265  const QString storedPassword = user.value(passwordField).toString();
266 
267  if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
268  if (!passwordPreSalt.isEmpty()) {
269  password.prepend(password);
270  }
271 
272  if (!passwordPostSalt.isEmpty()) {
273  password.append(password);
274  }
275 
276  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
277  } else if (passwordType == CredentialPassword::Clear) {
278  return storedPassword == password;
279  } else if (passwordType == CredentialPassword::None) {
280  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
281  return true;
282  }
283 
284  return false;
285 }
286 
287 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
288 {
289  QByteArray hashmethod;
290 
291 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
292  if (method == QCryptographicHash::Md4) {
293  hashmethod = QByteArrayLiteral("Md4");
294  } else if (method == QCryptographicHash::Md5) {
295  hashmethod = QByteArrayLiteral("Md5");
296  }
297 #endif
298  if (method == QCryptographicHash::Sha1) {
299  hashmethod = QByteArrayLiteral("Sha1");
300  }
301 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
302  if (method == QCryptographicHash::Sha224) {
303  hashmethod = QByteArrayLiteral("Sha224");
304  } else if (method == QCryptographicHash::Sha256) {
305  hashmethod = QByteArrayLiteral("Sha256");
306  } else if (method == QCryptographicHash::Sha384) {
307  hashmethod = QByteArrayLiteral("Sha384");
308  } else if (method == QCryptographicHash::Sha512) {
309  hashmethod = QByteArrayLiteral("Sha512");
310  } else if (method == QCryptographicHash::Sha3_224) {
311  hashmethod = QByteArrayLiteral("Sha3_224");
312  } else if (method == QCryptographicHash::Sha3_256) {
313  hashmethod = QByteArrayLiteral("Sha3_256");
314  } else if (method == QCryptographicHash::Sha3_384) {
315  hashmethod = QByteArrayLiteral("Sha3_384");
316  } else if (method == QCryptographicHash::Sha3_512) {
317  hashmethod = QByteArrayLiteral("Sha3_512");
318  }
319 #endif
320 
321  return hashmethod;
322 }
323 
324 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
325 {
326  QByteArray hashmethod = hashMethod;
327 
328  int method = -1;
329 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
330  if (hashmethod == "Md4") {
331  method = QCryptographicHash::Md4;
332  } else if (hashmethod == "Md5") {
333  method = QCryptographicHash::Md5;
334  }
335 #endif
336  if (hashmethod == "Sha1") {
337  method = QCryptographicHash::Sha1;
338  }
339 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
340  if (hashmethod == "Sha224") {
341  method = QCryptographicHash::Sha224;
342  } else if (hashmethod == "Sha256") {
343  method = QCryptographicHash::Sha256;
344  } else if (hashmethod == "Sha384") {
345  method = QCryptographicHash::Sha384;
346  } else if (hashmethod == "Sha512") {
347  method = QCryptographicHash::Sha512;
348  } else if (hashmethod == "Sha3_224") {
349  method = QCryptographicHash::Sha3_224;
350  } else if (hashmethod == "Sha3_256") {
351  method = QCryptographicHash::Sha3_256;
352  } else if (hashmethod == "Sha3_384") {
353  method = QCryptographicHash::Sha3_384;
354  } else if (hashmethod == "Sha3_512") {
355  method = QCryptographicHash::Sha3_512;
356  }
357 #endif
358 
359  return method;
360 }
361 
362 #include "moc_credentialpassword.cpp"
QMap< QString, QString > ParamsMultiMap
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Tries to find the user with authinfo returning a non null AuthenticationUser on success.
void setPasswordPreSalt(const QString &passwordPreSalt)
Sets the salt string to be prepended to the password.
bool isNull() const
Returns true if the object is null.
void setPasswordType(PasswordType type)
Sets the type of password this class will be dealing with.
static QByteArray createPassword(const QByteArray &password, QCryptographicHash::Algorithm method, int iterations, int saltByteSize, int hashByteSize)
Creates a password hash string.
The Cutelyst Context.
Definition: context.h:50
QString passwordField() const
Returns the field to look for when authenticating the user.
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setPasswordField(const QString &fieldName)
Sets the field to look for when authenticating the user.
QByteArray hmac(QCryptographicHash::Algorithm method, QByteArray key, const QByteArray &message)
Generates the Hash-based message authentication code.
PasswordType passwordType() const
Returns the type of password this class will be dealing with.
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
Generates a pbkdf2 string for the given password.
void setPasswordPostSalt(const QString &passwordPostSalt)
Sets the salt string to be appended to the password.
AuthenticationUser authenticate(Context *c, AuthenticationRealm *realm, const ParamsMultiMap &authinfo) final
Tries to authenticate the authinfo using the give realm.
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
Validates the given password against the correct hash.