Cutelyst  2.14.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 
88 void CredentialPassword::setPasswordPreSalt(const QString &passwordPreSalt)
89 {
90  Q_D(CredentialPassword);
91  d->passwordPreSalt = passwordPreSalt;
92 }
93 
95 {
96  Q_D(const CredentialPassword);
97  return d->passwordPostSalt;
98 }
99 
100 void CredentialPassword::setPasswordPostSalt(const QString &passwordPostSalt)
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, const QByteArray &key, const QByteArray &message)
234 {
235  return QMessageAuthenticationCode::hash(key, message, method);
236 }
237 
238 bool CredentialPasswordPrivate::checkPassword(const AuthenticationUser &user, const ParamsMultiMap &authinfo)
239 {
240  QString password = authinfo.value(passwordField);
241  const QString storedPassword = user.value(passwordField).toString();
242 
243  if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
244  if (!passwordPreSalt.isEmpty()) {
245  password.prepend(password);
246  }
247 
248  if (!passwordPostSalt.isEmpty()) {
249  password.append(password);
250  }
251 
252  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
253  } else if (passwordType == CredentialPassword::Clear) {
254  return storedPassword == password;
255  } else if (passwordType == CredentialPassword::None) {
256  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
257  return true;
258  }
259 
260  return false;
261 }
262 
263 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
264 {
265  QByteArray hashmethod;
266 
267 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
268  if (method == QCryptographicHash::Md4) {
269  hashmethod = QByteArrayLiteral("Md4");
270  } else if (method == QCryptographicHash::Md5) {
271  hashmethod = QByteArrayLiteral("Md5");
272  }
273 #endif
274  if (method == QCryptographicHash::Sha1) {
275  hashmethod = QByteArrayLiteral("Sha1");
276  }
277 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
278  if (method == QCryptographicHash::Sha224) {
279  hashmethod = QByteArrayLiteral("Sha224");
280  } else if (method == QCryptographicHash::Sha256) {
281  hashmethod = QByteArrayLiteral("Sha256");
282  } else if (method == QCryptographicHash::Sha384) {
283  hashmethod = QByteArrayLiteral("Sha384");
284  } else if (method == QCryptographicHash::Sha512) {
285  hashmethod = QByteArrayLiteral("Sha512");
286  } else if (method == QCryptographicHash::Sha3_224) {
287  hashmethod = QByteArrayLiteral("Sha3_224");
288  } else if (method == QCryptographicHash::Sha3_256) {
289  hashmethod = QByteArrayLiteral("Sha3_256");
290  } else if (method == QCryptographicHash::Sha3_384) {
291  hashmethod = QByteArrayLiteral("Sha3_384");
292  } else if (method == QCryptographicHash::Sha3_512) {
293  hashmethod = QByteArrayLiteral("Sha3_512");
294  }
295 #endif
296 
297  return hashmethod;
298 }
299 
300 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
301 {
302  QByteArray hashmethod = hashMethod;
303 
304  int method = -1;
305 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
306  if (hashmethod == "Md4") {
307  method = QCryptographicHash::Md4;
308  } else if (hashmethod == "Md5") {
309  method = QCryptographicHash::Md5;
310  }
311 #endif
312  if (hashmethod == "Sha1") {
313  method = QCryptographicHash::Sha1;
314  }
315 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
316  if (hashmethod == "Sha224") {
317  method = QCryptographicHash::Sha224;
318  } else if (hashmethod == "Sha256") {
319  method = QCryptographicHash::Sha256;
320  } else if (hashmethod == "Sha384") {
321  method = QCryptographicHash::Sha384;
322  } else if (hashmethod == "Sha512") {
323  method = QCryptographicHash::Sha512;
324  } else if (hashmethod == "Sha3_224") {
325  method = QCryptographicHash::Sha3_224;
326  } else if (hashmethod == "Sha3_256") {
327  method = QCryptographicHash::Sha3_256;
328  } else if (hashmethod == "Sha3_384") {
329  method = QCryptographicHash::Sha3_384;
330  } else if (hashmethod == "Sha3_512") {
331  method = QCryptographicHash::Sha3_512;
332  }
333 #endif
334 
335  return method;
336 }
337 
338 #include "moc_credentialpassword.cpp"
Cutelyst::CredentialPassword::passwordField
QString passwordField() const
Returns the field to look for when authenticating the user.
Definition: credentialpassword.cpp:58
Cutelyst::CredentialPassword::passwordPostSalt
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
Definition: credentialpassword.cpp:94
QMap::value
const T value(const Key &key, const T &defaultValue) const const
Cutelyst::AuthenticationUser::isNull
bool isNull() const
Returns true if the object is null.
Definition: authenticationuser.cpp:49
Cutelyst::AuthenticationCredential
Definition: authentication.h:31
Cutelyst::Context
The Cutelyst Context.
Definition: context.h:51
Cutelyst::CredentialPassword::setPasswordField
void setPasswordField(const QString &fieldName)
Sets the field to look for when authenticating the user.
Definition: credentialpassword.cpp:64
Cutelyst::AuthenticationUser
Definition: authenticationuser.h:31
Cutelyst::CredentialPassword::passwordType
PasswordType passwordType() const
Returns the type of password this class will be dealing with.
Definition: credentialpassword.cpp:70
QByteArrayList
Cutelyst::CredentialPassword::passwordPreSalt
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
Definition: credentialpassword.cpp:82
Cutelyst::CredentialPassword::validatePassword
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
Validates the given password against the correct hash.
Definition: credentialpassword.cpp:121
Cutelyst::CredentialPassword::createPassword
static QByteArray createPassword(const QByteArray &password, QCryptographicHash::Algorithm method, int iterations, int saltByteSize, int hashByteSize)
Creates a password hash string.
Definition: credentialpassword.cpp:146
Cutelyst::AuthenticationRealm
Definition: authenticationrealm.h:30
QMap
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::CredentialPassword::setPasswordPreSalt
void setPasswordPreSalt(const QString &passwordPreSalt)
Sets the salt string to be prepended to the password.
Definition: credentialpassword.cpp:88
Cutelyst::CredentialPassword::setPasswordType
void setPasswordType(PasswordType type)
Sets the type of password this class will be dealing with.
Definition: credentialpassword.cpp:76
Cutelyst::AuthenticationRealm::findUser
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Tries to find the user with authinfo returning a non null AuthenticationUser on success.
Definition: authenticationrealm.cpp:60
Cutelyst::CredentialPassword::authenticate
AuthenticationUser authenticate(Context *c, AuthenticationRealm *realm, const ParamsMultiMap &authinfo) final
Tries to authenticate the authinfo using the give realm.
Definition: credentialpassword.cpp:41
Cutelyst::CredentialPassword::pbkdf2
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
Generates a pbkdf2 string for the given password.
Definition: credentialpassword.cpp:180
Cutelyst::CredentialPassword::setPasswordPostSalt
void setPasswordPostSalt(const QString &passwordPostSalt)
Sets the salt string to be appended to the password.
Definition: credentialpassword.cpp:100
Cutelyst::CredentialPassword::hmac
static QByteArray hmac(QCryptographicHash::Algorithm method, const QByteArray &key, const QByteArray &message)
Generates the Hash-based message authentication code.
Definition: credentialpassword.cpp:233
Cutelyst::CredentialPassword
Definition: credentialpassword.h:29