cutelyst  5.0.1
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 using namespace Qt::StringLiterals;
15 
16 Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
17 
19  : AuthenticationCredential(parent)
20  , d_ptr(new CredentialPasswordPrivate)
21 {
22 }
23 
25 {
26  delete d_ptr;
27 }
28 
30  AuthenticationRealm *realm,
31  const ParamsMultiMap &authinfo)
32 {
33  AuthenticationUser user;
34  Q_D(CredentialPassword);
35  AuthenticationUser _user = realm->findUser(c, authinfo);
36  if (!_user.isNull()) {
37  if (d->checkPassword(_user, authinfo)) {
38  user = _user;
39  } else {
40  qCDebug(C_CREDENTIALPASSWORD) << "Password didn't match";
41  }
42  } else {
43  qCDebug(C_CREDENTIALPASSWORD)
44  << "Unable to locate a user matching user info provided in realm";
45  }
46  return user;
47 }
48 
50 {
51  Q_D(const CredentialPassword);
52  return d->passwordField;
53 }
54 
56 {
57  Q_D(CredentialPassword);
58  d->passwordField = fieldName;
59 }
60 
62 {
63  Q_D(const CredentialPassword);
64  return d->passwordType;
65 }
66 
68 {
69  Q_D(CredentialPassword);
70  d->passwordType = type;
71 }
72 
74 {
75  Q_D(const CredentialPassword);
76  return d->passwordPreSalt;
77 }
78 
79 void CredentialPassword::setPasswordPreSalt(const QString &passwordPreSalt)
80 {
81  Q_D(CredentialPassword);
82  d->passwordPreSalt = passwordPreSalt;
83 }
84 
86 {
87  Q_D(const CredentialPassword);
88  return d->passwordPostSalt;
89 }
90 
91 void CredentialPassword::setPasswordPostSalt(const QString &passwordPostSalt)
92 {
93  Q_D(CredentialPassword);
94  d->passwordPostSalt = passwordPostSalt;
95 }
96 
97 // To avoid timming attack
98 bool slowEquals(const QByteArray &a, const QByteArray &b)
99 {
100  int diff = a.size() ^ b.size();
101  for (int i = 0; i < a.size() && i < b.size(); i++) {
102  diff |= a[i] ^ b[i];
103  }
104  return diff == 0;
105 }
106 
107 namespace {
108 #define HASH_SECTIONS 4
109 #define HASH_ALGORITHM_INDEX 0
110 #define HASH_ITERATION_INDEX 1
111 #define HASH_SALT_INDEX 2
112 #define HASH_PBKDF2_INDEX 3
113 } // namespace
114 
115 bool CredentialPassword::validatePassword(const QByteArray &password, const QByteArray &correctHash)
116 {
117  QByteArrayList params = correctHash.split(':');
118  if (params.size() < HASH_SECTIONS) {
119  return false;
120  }
121 
122  int method = CredentialPasswordPrivate::cryptoStrToEnum(params.at(HASH_ALGORITHM_INDEX));
123  if (method == -1) {
124  return false;
125  }
126 
127  QByteArray pbkdf2Hash = QByteArray::fromBase64(params.at(HASH_PBKDF2_INDEX));
128  return slowEquals(pbkdf2Hash,
129  pbkdf2(static_cast<QCryptographicHash::Algorithm>(method),
130  password,
131  params.at(HASH_SALT_INDEX),
132  params.at(HASH_ITERATION_INDEX).toInt(),
133  pbkdf2Hash.length()));
134 }
135 
138  int iterations,
139  int saltByteSize,
140  int hashByteSize)
141 {
142  QByteArray salt;
143 #ifdef Q_OS_LINUX
144  QFile random(u"/dev/urandom"_s);
145  if (random.open(QIODevice::ReadOnly)) {
146  salt = random.read(saltByteSize).toBase64();
147  } else {
148 #endif
149  salt = QUuid::createUuid().toRfc4122().toBase64();
150 #ifdef Q_OS_LINUX
151  }
152 #endif
153 
154  const QByteArray methodStr = CredentialPasswordPrivate::cryptoEnumToStr(method);
155  return methodStr + ':' + QByteArray::number(iterations) + ':' + salt + ':' +
156  pbkdf2(method, password, salt, iterations, hashByteSize).toBase64();
157 }
158 
160 {
161  return createPassword(password, QCryptographicHash::Sha512, 10000, 16, 16);
162 }
163 
164 // TODO https://crackstation.net/hashing-security.htm
165 // shows a different Algorithm that seems a bit simpler
166 // this one does passes the RFC6070 tests
167 // https://www.ietf.org/rfc/rfc6070.txt
169  const QByteArray &password,
170  const QByteArray &salt,
171  int rounds,
172  int keyLength)
173 {
174  QByteArray key;
175 
176  if (rounds <= 0 || keyLength <= 0) {
177  qCCritical(C_CREDENTIALPASSWORD, "PBKDF2 ERROR: Invalid parameters.");
178  return key;
179  }
180 
181  if (salt.size() == 0 || salt.size() > std::numeric_limits<int>::max() - 4) {
182  return key;
183  }
184  key.reserve(keyLength);
185 
186  int saltSize = salt.size();
187  QByteArray asalt = salt;
188  asalt.resize(saltSize + 4);
189 
190  QByteArray d1;
191  QByteArray obuf;
192 
193  QMessageAuthenticationCode code(method, password);
194 
195  for (int count = 1, remainingBytes = keyLength; remainingBytes > 0; ++count) {
196  asalt[saltSize + 0] = static_cast<char>((count >> 24) & 0xff);
197  asalt[saltSize + 1] = static_cast<char>((count >> 16) & 0xff);
198  asalt[saltSize + 2] = static_cast<char>((count >> 8) & 0xff);
199  asalt[saltSize + 3] = static_cast<char>(count & 0xff);
200 
201  code.reset();
202  code.addData(asalt);
203  obuf = d1 = code.result();
204 
205  for (int i = 1; i < rounds; ++i) {
206  code.reset();
207  code.addData(d1);
208  d1 = code.result();
209  auto it = obuf.begin();
210  auto d1It = d1.cbegin();
211  while (d1It != d1.cend()) {
212  *it = *it ^ *d1It;
213  ++it;
214  ++d1It;
215  }
216  }
217 
218  key.append(obuf);
219  remainingBytes -= obuf.size();
220  }
221 
222  key.truncate(keyLength);
223  return key;
224 }
225 
227  const QByteArray &key,
228  const QByteArray &message)
229 {
230  return QMessageAuthenticationCode::hash(key, message, method);
231 }
232 
233 bool CredentialPasswordPrivate::checkPassword(const AuthenticationUser &user,
234  const ParamsMultiMap &authinfo)
235 {
236  const QString password = passwordPreSalt + authinfo.value(passwordField) + passwordPostSalt;
237  const QString storedPassword = user.value(passwordField).toString();
238 
239  if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
240  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
241  } else if (passwordType == CredentialPassword::Clear) {
242  return storedPassword == password;
243  } else if (passwordType == CredentialPassword::None) {
244  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
245  return true;
246  }
247 
248  return false;
249 }
250 
251 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
252 {
253  QByteArray hashmethod;
254 
255 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
256  if (method == QCryptographicHash::Md4) {
257  hashmethod = QByteArrayLiteral("Md4");
258  } else if (method == QCryptographicHash::Md5) {
259  hashmethod = QByteArrayLiteral("Md5");
260  }
261 #endif
262  if (method == QCryptographicHash::Sha1) {
263  hashmethod = QByteArrayLiteral("Sha1");
264  }
265 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
266  if (method == QCryptographicHash::Sha224) {
267  hashmethod = QByteArrayLiteral("Sha224");
268  } else if (method == QCryptographicHash::Sha256) {
269  hashmethod = QByteArrayLiteral("Sha256");
270  } else if (method == QCryptographicHash::Sha384) {
271  hashmethod = QByteArrayLiteral("Sha384");
272  } else if (method == QCryptographicHash::Sha512) {
273  hashmethod = QByteArrayLiteral("Sha512");
274  } else if (method == QCryptographicHash::Sha3_224) {
275  hashmethod = QByteArrayLiteral("Sha3_224");
276  } else if (method == QCryptographicHash::Sha3_256) {
277  hashmethod = QByteArrayLiteral("Sha3_256");
278  } else if (method == QCryptographicHash::Sha3_384) {
279  hashmethod = QByteArrayLiteral("Sha3_384");
280  } else if (method == QCryptographicHash::Sha3_512) {
281  hashmethod = QByteArrayLiteral("Sha3_512");
282  }
283 #endif
284 
285  return hashmethod;
286 }
287 
288 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
289 {
290  QByteArray hashmethod = hashMethod;
291 
292  int method = -1;
293 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
294  if (hashmethod == "Md4") {
295  method = QCryptographicHash::Md4;
296  } else if (hashmethod == "Md5") {
297  method = QCryptographicHash::Md5;
298  }
299 #endif
300  if (hashmethod == "Sha1") {
301  method = QCryptographicHash::Sha1;
302  }
303 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
304  if (hashmethod == "Sha224") {
306  } else if (hashmethod == "Sha256") {
308  } else if (hashmethod == "Sha384") {
310  } else if (hashmethod == "Sha512") {
312  } else if (hashmethod == "Sha3_224") {
314  } else if (hashmethod == "Sha3_256") {
316  } else if (hashmethod == "Sha3_384") {
318  } else if (hashmethod == "Sha3_512") {
320  }
321 #endif
322 
323  return method;
324 }
325 
326 #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
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