cutelyst  4.4.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
credentialpassword.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "authenticationrealm.h"
6 #include "credentialpassword_p.h"
7 
8 #include <QFile>
9 #include <QLoggingCategory>
10 #include <QMessageAuthenticationCode>
11 #include <QUuid>
12 
13 using namespace Cutelyst;
14 
15 Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
16 
18  : AuthenticationCredential(parent)
19  , d_ptr(new CredentialPasswordPrivate)
20 {
21 }
22 
24 {
25  delete d_ptr;
26 }
27 
29  AuthenticationRealm *realm,
30  const ParamsMultiMap &authinfo)
31 {
32  AuthenticationUser user;
33  Q_D(CredentialPassword);
34  AuthenticationUser _user = realm->findUser(c, authinfo);
35  if (!_user.isNull()) {
36  if (d->checkPassword(_user, authinfo)) {
37  user = _user;
38  } else {
39  qCDebug(C_CREDENTIALPASSWORD) << "Password didn't match";
40  }
41  } else {
42  qCDebug(C_CREDENTIALPASSWORD)
43  << "Unable to locate a user matching user info provided in realm";
44  }
45  return user;
46 }
47 
49 {
50  Q_D(const CredentialPassword);
51  return d->passwordField;
52 }
53 
55 {
56  Q_D(CredentialPassword);
57  d->passwordField = fieldName;
58 }
59 
61 {
62  Q_D(const CredentialPassword);
63  return d->passwordType;
64 }
65 
67 {
68  Q_D(CredentialPassword);
69  d->passwordType = type;
70 }
71 
73 {
74  Q_D(const CredentialPassword);
75  return d->passwordPreSalt;
76 }
77 
78 void CredentialPassword::setPasswordPreSalt(const QString &passwordPreSalt)
79 {
80  Q_D(CredentialPassword);
81  d->passwordPreSalt = passwordPreSalt;
82 }
83 
85 {
86  Q_D(const CredentialPassword);
87  return d->passwordPostSalt;
88 }
89 
90 void CredentialPassword::setPasswordPostSalt(const QString &passwordPostSalt)
91 {
92  Q_D(CredentialPassword);
93  d->passwordPostSalt = passwordPostSalt;
94 }
95 
96 // To avoid timming attack
97 bool slowEquals(const QByteArray &a, const QByteArray &b)
98 {
99  int diff = a.size() ^ b.size();
100  for (int i = 0; i < a.size() && i < b.size(); i++) {
101  diff |= a[i] ^ b[i];
102  }
103  return diff == 0;
104 }
105 
106 #define HASH_SECTIONS 4
107 #define HASH_ALGORITHM_INDEX 0
108 #define HASH_ITERATION_INDEX 1
109 #define HASH_SALT_INDEX 2
110 #define HASH_PBKDF2_INDEX 3
111 bool CredentialPassword::validatePassword(const QByteArray &password, const QByteArray &correctHash)
112 {
113  QByteArrayList params = correctHash.split(':');
114  if (params.size() < HASH_SECTIONS) {
115  return false;
116  }
117 
118  int method = CredentialPasswordPrivate::cryptoStrToEnum(params.at(HASH_ALGORITHM_INDEX));
119  if (method == -1) {
120  return false;
121  }
122 
123  QByteArray pbkdf2Hash = QByteArray::fromBase64(params.at(HASH_PBKDF2_INDEX));
124  return slowEquals(pbkdf2Hash,
125  pbkdf2(static_cast<QCryptographicHash::Algorithm>(method),
126  password,
127  params.at(HASH_SALT_INDEX),
128  params.at(HASH_ITERATION_INDEX).toInt(),
129  pbkdf2Hash.length()));
130 }
131 
134  int iterations,
135  int saltByteSize,
136  int hashByteSize)
137 {
138  QByteArray salt;
139 #ifdef Q_OS_LINUX
140  QFile random(QStringLiteral("/dev/urandom"));
141  if (random.open(QIODevice::ReadOnly)) {
142  salt = random.read(saltByteSize).toBase64();
143  } else {
144 #endif
145  salt = QUuid::createUuid().toRfc4122().toBase64();
146 #ifdef Q_OS_LINUX
147  }
148 #endif
149 
150  const QByteArray methodStr = CredentialPasswordPrivate::cryptoEnumToStr(method);
151  return methodStr + ':' + QByteArray::number(iterations) + ':' + salt + ':' +
152  pbkdf2(method, password, salt, iterations, hashByteSize).toBase64();
153 }
154 
156 {
157  return createPassword(password, QCryptographicHash::Sha512, 10000, 16, 16);
158 }
159 
160 // TODO https://crackstation.net/hashing-security.htm
161 // shows a different Algorithm that seems a bit simpler
162 // this one does passes the RFC6070 tests
163 // https://www.ietf.org/rfc/rfc6070.txt
165  const QByteArray &password,
166  const QByteArray &salt,
167  int rounds,
168  int keyLength)
169 {
170  QByteArray key;
171 
172  if (rounds <= 0 || keyLength <= 0) {
173  qCCritical(C_CREDENTIALPASSWORD, "PBKDF2 ERROR: Invalid parameters.");
174  return key;
175  }
176 
177  if (salt.size() == 0 || salt.size() > std::numeric_limits<int>::max() - 4) {
178  return key;
179  }
180  key.reserve(keyLength);
181 
182  int saltSize = salt.size();
183  QByteArray asalt = salt;
184  asalt.resize(saltSize + 4);
185 
186  QByteArray d1, obuf;
187 
188  QMessageAuthenticationCode code(method, password);
189 
190  for (int count = 1, remainingBytes = keyLength; remainingBytes > 0; ++count) {
191  asalt[saltSize + 0] = static_cast<char>((count >> 24) & 0xff);
192  asalt[saltSize + 1] = static_cast<char>((count >> 16) & 0xff);
193  asalt[saltSize + 2] = static_cast<char>((count >> 8) & 0xff);
194  asalt[saltSize + 3] = static_cast<char>(count & 0xff);
195 
196  code.reset();
197  code.addData(asalt);
198  obuf = d1 = code.result();
199 
200  for (int i = 1; i < rounds; ++i) {
201  code.reset();
202  code.addData(d1);
203  d1 = code.result();
204  auto it = obuf.begin();
205  auto d1It = d1.cbegin();
206  while (d1It != d1.cend()) {
207  *it = *it ^ *d1It;
208  ++it;
209  ++d1It;
210  }
211  }
212 
213  key.append(obuf);
214  remainingBytes -= obuf.size();
215  }
216 
217  key.truncate(keyLength);
218  return key;
219 }
220 
222  const QByteArray &key,
223  const QByteArray &message)
224 {
225  return QMessageAuthenticationCode::hash(key, message, method);
226 }
227 
228 bool CredentialPasswordPrivate::checkPassword(const AuthenticationUser &user,
229  const ParamsMultiMap &authinfo)
230 {
231  const QString password = passwordPreSalt + authinfo.value(passwordField) + passwordPostSalt;
232  const QString storedPassword = user.value(passwordField).toString();
233 
234  if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
235  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
236  } else if (passwordType == CredentialPassword::Clear) {
237  return storedPassword == password;
238  } else if (passwordType == CredentialPassword::None) {
239  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
240  return true;
241  }
242 
243  return false;
244 }
245 
246 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
247 {
248  QByteArray hashmethod;
249 
250 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
251  if (method == QCryptographicHash::Md4) {
252  hashmethod = QByteArrayLiteral("Md4");
253  } else if (method == QCryptographicHash::Md5) {
254  hashmethod = QByteArrayLiteral("Md5");
255  }
256 #endif
257  if (method == QCryptographicHash::Sha1) {
258  hashmethod = QByteArrayLiteral("Sha1");
259  }
260 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
261  if (method == QCryptographicHash::Sha224) {
262  hashmethod = QByteArrayLiteral("Sha224");
263  } else if (method == QCryptographicHash::Sha256) {
264  hashmethod = QByteArrayLiteral("Sha256");
265  } else if (method == QCryptographicHash::Sha384) {
266  hashmethod = QByteArrayLiteral("Sha384");
267  } else if (method == QCryptographicHash::Sha512) {
268  hashmethod = QByteArrayLiteral("Sha512");
269  } else if (method == QCryptographicHash::Sha3_224) {
270  hashmethod = QByteArrayLiteral("Sha3_224");
271  } else if (method == QCryptographicHash::Sha3_256) {
272  hashmethod = QByteArrayLiteral("Sha3_256");
273  } else if (method == QCryptographicHash::Sha3_384) {
274  hashmethod = QByteArrayLiteral("Sha3_384");
275  } else if (method == QCryptographicHash::Sha3_512) {
276  hashmethod = QByteArrayLiteral("Sha3_512");
277  }
278 #endif
279 
280  return hashmethod;
281 }
282 
283 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
284 {
285  QByteArray hashmethod = hashMethod;
286 
287  int method = -1;
288 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
289  if (hashmethod == "Md4") {
290  method = QCryptographicHash::Md4;
291  } else if (hashmethod == "Md5") {
292  method = QCryptographicHash::Md5;
293  }
294 #endif
295  if (hashmethod == "Sha1") {
296  method = QCryptographicHash::Sha1;
297  }
298 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
299  if (hashmethod == "Sha224") {
301  } else if (hashmethod == "Sha256") {
303  } else if (hashmethod == "Sha384") {
305  } else if (hashmethod == "Sha512") {
307  } else if (hashmethod == "Sha3_224") {
309  } else if (hashmethod == "Sha3_256") {
311  } else if (hashmethod == "Sha3_384") {
313  } else if (hashmethod == "Sha3_512") {
315  }
316 #endif
317 
318  return method;
319 }
320 
321 #include "moc_credentialpassword.cpp"
Abstract class to validate authentication credentials like user name and password.
Combines user store and credential validation into a named realm.
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Container for user data retrieved from an AuthenticationStore.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
The Cutelyst Context.
Definition: context.h:42
Use password based authentication to authenticate a user.
void setPasswordType(PasswordType type)
void setPasswordPostSalt(const QString &passwordPostSalt)
virtual ~CredentialPassword() override
AuthenticationUser authenticate(Context *c, AuthenticationRealm *realm, const ParamsMultiMap &authinfo) final
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
PasswordType passwordType() const
static QByteArray createPassword(const QByteArray &password, QCryptographicHash::Algorithm method, int iterations, int saltByteSize, int hashByteSize)
void setPasswordField(const QString &fieldName)
static QByteArray hmac(QCryptographicHash::Algorithm method, const QByteArray &key, const QByteArray &message)
void setPasswordPreSalt(const QString &passwordPreSalt)
The Cutelyst namespace holds all public Cutelyst API.
QByteArray & append(QByteArrayView data)
QByteArray::iterator begin()
QByteArray::const_iterator cbegin() const const
QByteArray::const_iterator cend() const const
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
qsizetype length() const const
QByteArray number(double n, char format, int precision)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
QList< QByteArray > split(char sep) const const
QByteArray toBase64(QByteArray::Base64Options options) const const
void truncate(qsizetype pos)
bool open(FILE *fh, QIODeviceBase::OpenMode mode, QFileDevice::FileHandleFlags handleFlags)
QByteArray read(qint64 maxSize)
QList::const_reference at(qsizetype i) const const
qsizetype size() const const
bool addData(QIODevice *device)
QByteArray hash(QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method)
QByteArray result() const const
T value(const Key &key, const T &defaultValue) const const
QByteArray toUtf8() const const
QUuid createUuid()
QByteArray toRfc4122() const const
QString toString() const const