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