Cutelyst  2.14.2
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 
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 
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 
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"
const_iterator cbegin() const
QString & append(QChar ch)
QList< QByteArray > split(char sep) const
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Tries to find the user with authinfo returning a non null AuthenticationUser on success.
const_iterator cend() const
void reserve(int size)
void setPasswordPreSalt(const QString &passwordPreSalt)
Sets the salt string to be prepended to the password.
QString & prepend(QChar ch)
const T & at(int i) const
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
static QByteArray hmac(QCryptographicHash::Algorithm method, const 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.
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
int length() const
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
int size() const
void setPasswordType(PasswordType type)
Sets the type of password this class will be dealing with.
void resize(int size)
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:51
QByteArray result() const
void addData(const char *data, int length)
QByteArray number(int n, int base)
iterator begin()
qint64 read(char *data, qint64 maxSize)
QByteArray mid(int pos, int len) const
virtual bool open(OpenMode mode)
QByteArray & append(char ch)
QByteArray hash(const QByteArray &message, const QByteArray &key, QCryptographicHash::Algorithm method)
void setPasswordField(const QString &fieldName)
Sets the field to look for when authenticating the user.
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.
int size() const
QString toString() const
QByteArray toRfc4122() const
QByteArray toBase64(Base64Options options) const
QUuid createUuid()
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
Validates the given password against the correct hash.
const T value(const Key &key, const T &defaultValue) const
QByteArray toUtf8() const