Cutelyst  1.11.0
credentialpassword.cpp
1 /*
2  * Copyright (C) 2013-2017 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")
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::Type CredentialPassword::passwordType() const
71 {
72  Q_D(const CredentialPassword);
73  return d->passwordType;
74 }
75 
76 void CredentialPassword::setPasswordType(CredentialPassword::Type 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  QFile random(QStringLiteral("/dev/urandom"));
150  if (!random.open(QIODevice::ReadOnly)) {
151  salt = QUuid::createUuid().toByteArray().toBase64();
152  } else {
153  salt = random.read(saltByteSize).toBase64();
154  }
155 
156  const QByteArray methodStr = CredentialPasswordPrivate::cryptoEnumToStr(method);
157  return methodStr + ':' + QByteArray::number(iterations) + ':' + salt + ':' +
158  pbkdf2(
159  method,
160  password,
161  salt,
162  iterations,
163  hashByteSize
164  ).toBase64();
165 }
166 
167 // TODO https://crackstation.net/hashing-security.htm
168 // shows a different Algorithm that seems a bit simpler
169 // this one does passes the RFC6070 tests
170 // https://www.ietf.org/rfc/rfc6070.txt
171 QByteArray CredentialPassword::pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
172 {
173  QByteArray key;
174 
175  if (rounds <= 0 || keyLength <= 0) {
176  qCCritical(C_CREDENTIALPASSWORD, "PBKDF2 ERROR: Invalid parameters.");
177  return key;
178  }
179 
180  if (salt.size() == 0 || salt.size() > std::numeric_limits<int>::max() - 4) {
181  return key;
182  }
183  key.reserve(keyLength);
184 
185  int saltSize = salt.size();
186  QByteArray asalt = salt;
187  asalt.resize(saltSize + 4);
188 
189  QByteArray d1, obuf;
190 
191  QMessageAuthenticationCode code(method, password);
192 
193  for (int count = 1, remainingBytes = keyLength; remainingBytes > 0; ++count) {
194  asalt[saltSize + 0] = static_cast<char>((count >> 24) & 0xff);
195  asalt[saltSize + 1] = static_cast<char>((count >> 16) & 0xff);
196  asalt[saltSize + 2] = static_cast<char>((count >> 8) & 0xff);
197  asalt[saltSize + 3] = static_cast<char>(count & 0xff);
198 
199  code.reset();
200  code.addData(asalt);
201  obuf = d1 = code.result();
202 
203  for (int i = 1; i < rounds; ++i) {
204  code.reset();
205  code.addData(d1);
206  d1 = code.result();
207  auto it = obuf.begin();
208  auto d1It = d1.cbegin();
209  while (d1It != d1.cend()) {
210  *it = *it ^ *d1It;
211  ++it;
212  ++d1It;
213  }
214  }
215 
216  key.append(obuf);
217  remainingBytes -= obuf.size();
218  }
219 
220  key = key.mid(0, keyLength);
221  return key;
222 }
223 
224 QByteArray CredentialPassword::hmac(QCryptographicHash::Algorithm method, QByteArray key, const QByteArray &message)
225 {
226  QByteArray ret;
227  const int blocksize = 64;
228  if (key.length() > blocksize) {
229  ret = QCryptographicHash::hash(key, method);
230  return ret;
231  }
232 
233  while (key.length() < blocksize) {
234  key.append('\0');
235  }
236 
237  QByteArray o_key_pad('\x5c', blocksize);
238  o_key_pad.fill('\x5c', blocksize);
239 
240  QByteArray i_key_pad;
241  i_key_pad.fill('\x36', blocksize);
242 
243  for (int i=0; i < blocksize; i++) {
244  o_key_pad[i] = o_key_pad[i] ^ key[i];
245  i_key_pad[i] = i_key_pad[i] ^ key[i];
246  }
247 
248  ret = QCryptographicHash::hash(o_key_pad + QCryptographicHash::hash(i_key_pad + message, method),
249  method);
250  return ret;
251 }
252 
253 bool CredentialPasswordPrivate::checkPassword(const AuthenticationUser &user, const ParamsMultiMap &authinfo)
254 {
255  QString password = authinfo.value(passwordField);
256  const QString storedPassword = user.value(passwordField).toString();
257 
258  if (passwordType == CredentialPassword::None) {
259  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
260  return true;
261  } else if (passwordType == CredentialPassword::Clear) {
262  return storedPassword == password;
263  } else if (passwordType == CredentialPassword::Hashed) {
264  if (!passwordPreSalt.isEmpty()) {
265  password.prepend(password);
266  }
267 
268  if (!passwordPostSalt.isEmpty()) {
269  password.append(password);
270  }
271 
272  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
273  } else if (passwordType == CredentialPassword::SelfCheck) {
274  return user.checkPassword(password);
275  }
276 
277  return false;
278 }
279 
280 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
281 {
282  QByteArray hashmethod;
283 
284 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
285  if (method == QCryptographicHash::Md4) {
286  hashmethod = QByteArrayLiteral("Md4");
287  } else if (method == QCryptographicHash::Md5) {
288  hashmethod = QByteArrayLiteral("Md5");
289  }
290 #endif
291  if (method == QCryptographicHash::Sha1) {
292  hashmethod = QByteArrayLiteral("Sha1");
293  }
294 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
295  if (method == QCryptographicHash::Sha224) {
296  hashmethod = QByteArrayLiteral("Sha224");
297  } else if (method == QCryptographicHash::Sha256) {
298  hashmethod = QByteArrayLiteral("Sha256");
299  } else if (method == QCryptographicHash::Sha384) {
300  hashmethod = QByteArrayLiteral("Sha384");
301  } else if (method == QCryptographicHash::Sha512) {
302  hashmethod = QByteArrayLiteral("Sha512");
303  } else if (method == QCryptographicHash::Sha3_224) {
304  hashmethod = QByteArrayLiteral("Sha3_224");
305  } else if (method == QCryptographicHash::Sha3_256) {
306  hashmethod = QByteArrayLiteral("Sha3_256");
307  } else if (method == QCryptographicHash::Sha3_384) {
308  hashmethod = QByteArrayLiteral("Sha3_384");
309  } else if (method == QCryptographicHash::Sha3_512) {
310  hashmethod = QByteArrayLiteral("Sha3_512");
311  }
312 #endif
313 
314  return hashmethod;
315 }
316 
317 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
318 {
319  QByteArray hashmethod = hashMethod;
320 
321  int method = -1;
322 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
323  if (hashmethod == "Md4") {
324  method = QCryptographicHash::Md4;
325  } else if (hashmethod == "Md5") {
326  method = QCryptographicHash::Md5;
327  }
328 #endif
329  if (hashmethod == "Sha1") {
330  method = QCryptographicHash::Sha1;
331  }
332 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
333  if (hashmethod == "Sha224") {
334  method = QCryptographicHash::Sha224;
335  } else if (hashmethod == "Sha256") {
336  method = QCryptographicHash::Sha256;
337  } else if (hashmethod == "Sha384") {
338  method = QCryptographicHash::Sha384;
339  } else if (hashmethod == "Sha512") {
340  method = QCryptographicHash::Sha512;
341  } else if (hashmethod == "Sha3_224") {
342  method = QCryptographicHash::Sha3_224;
343  } else if (hashmethod == "Sha3_256") {
344  method = QCryptographicHash::Sha3_256;
345  } else if (hashmethod == "Sha3_384") {
346  method = QCryptographicHash::Sha3_384;
347  } else if (hashmethod == "Sha3_512") {
348  method = QCryptographicHash::Sha3_512;
349  }
350 #endif
351 
352  return method;
353 }
354 
355 #include "moc_credentialpassword.cpp"
QMap< QString, QString > ParamsMultiMap
virtual bool checkPassword(const QString &password) const
Verifies the user 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.
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
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
Type passwordType() const
Returns the type of password this class will be dealing with.
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.
bool isNull() const
Returns true if the object is null.
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
Generates a pbkdf2 string for the given password.
QString passwordField() const
Returns the field to look for when authenticating the user.
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.
void setPasswordType(Type type)
Sets the type of password this class will be dealing with.
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
Validates the given password against the correct hash.