cutelyst  3.9.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 
15 Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
16 
18  : AuthenticationCredential(parent)
19  , d_ptr(new CredentialPasswordPrivate)
20 {
21 }
22 
23 CredentialPassword::~CredentialPassword()
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 
60 CredentialPassword::PasswordType CredentialPassword::passwordType() const
61 {
62  Q_D(const CredentialPassword);
63  return d->passwordType;
64 }
65 
66 void CredentialPassword::setPasswordType(Cutelyst::CredentialPassword::PasswordType type)
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  QString password = authinfo.value(passwordField);
232  const QString storedPassword = user.value(passwordField).toString();
233 
234  if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
235  if (!passwordPreSalt.isEmpty()) {
236  password.prepend(password);
237  }
238 
239  if (!passwordPostSalt.isEmpty()) {
240  password.append(password);
241  }
242 
243  return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
244  } else if (passwordType == CredentialPassword::Clear) {
245  return storedPassword == password;
246  } else if (passwordType == CredentialPassword::None) {
247  qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
248  return true;
249  }
250 
251  return false;
252 }
253 
254 QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
255 {
256  QByteArray hashmethod;
257 
258 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
259  if (method == QCryptographicHash::Md4) {
260  hashmethod = QByteArrayLiteral("Md4");
261  } else if (method == QCryptographicHash::Md5) {
262  hashmethod = QByteArrayLiteral("Md5");
263  }
264 #endif
265  if (method == QCryptographicHash::Sha1) {
266  hashmethod = QByteArrayLiteral("Sha1");
267  }
268 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
269  if (method == QCryptographicHash::Sha224) {
270  hashmethod = QByteArrayLiteral("Sha224");
271  } else if (method == QCryptographicHash::Sha256) {
272  hashmethod = QByteArrayLiteral("Sha256");
273  } else if (method == QCryptographicHash::Sha384) {
274  hashmethod = QByteArrayLiteral("Sha384");
275  } else if (method == QCryptographicHash::Sha512) {
276  hashmethod = QByteArrayLiteral("Sha512");
277  } else if (method == QCryptographicHash::Sha3_224) {
278  hashmethod = QByteArrayLiteral("Sha3_224");
279  } else if (method == QCryptographicHash::Sha3_256) {
280  hashmethod = QByteArrayLiteral("Sha3_256");
281  } else if (method == QCryptographicHash::Sha3_384) {
282  hashmethod = QByteArrayLiteral("Sha3_384");
283  } else if (method == QCryptographicHash::Sha3_512) {
284  hashmethod = QByteArrayLiteral("Sha3_512");
285  }
286 #endif
287 
288  return hashmethod;
289 }
290 
291 int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
292 {
293  QByteArray hashmethod = hashMethod;
294 
295  int method = -1;
296 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
297  if (hashmethod == "Md4") {
298  method = QCryptographicHash::Md4;
299  } else if (hashmethod == "Md5") {
300  method = QCryptographicHash::Md5;
301  }
302 #endif
303  if (hashmethod == "Sha1") {
304  method = QCryptographicHash::Sha1;
305  }
306 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
307  if (hashmethod == "Sha224") {
309  } else if (hashmethod == "Sha256") {
311  } else if (hashmethod == "Sha384") {
313  } else if (hashmethod == "Sha512") {
315  } else if (hashmethod == "Sha3_224") {
317  } else if (hashmethod == "Sha3_256") {
319  } else if (hashmethod == "Sha3_384") {
321  } else if (hashmethod == "Sha3_512") {
323  }
324 #endif
325 
326  return method;
327 }
328 
329 #include "moc_credentialpassword.cpp"
QByteArray::const_iterator cbegin() const const
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
QString & append(QChar ch)
QList< QByteArray > split(char sep) const const
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Tries to find the user with authinfo returning a non null AuthenticationUser on success.
QByteArray::const_iterator cend() const const
void reserve(int size)
void setPasswordPreSalt(const QString &passwordPreSalt)
Sets the salt string to be prepended to the password.
QString & prepend(QChar ch)
bool isNull() const
Returns true if the object is null.
const T & at(int i) const const
static QByteArray hmac(QCryptographicHash::Algorithm method, const QByteArray &key, const QByteArray &message)
Generates the Hash-based message authentication code.
int length() const const
int size() const 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:38
QByteArray result() const const
QString passwordField() const
Returns the field to look for when authenticating the user.
void addData(const char *data, int length)
QByteArray number(int n, int base)
QByteArray::iterator begin()
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
qint64 read(char *data, qint64 maxSize)
void truncate(int pos)
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
virtual bool open(QIODevice::OpenMode mode) override
QByteArray & append(char ch)
QByteArray hash(const QByteArray &message, const QByteArray &key, QCryptographicHash::Algorithm method)
QByteArray fromBase64(const QByteArray &base64, QByteArray::Base64Options options)
void setPasswordField(const QString &fieldName)
Sets the field to look for when authenticating the user.
PasswordType passwordType() const
Returns the type of password this class will be dealing with.
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
Generates a pbkdf2 string for the given password.
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 const
QString toString() const const
QByteArray toRfc4122() const const
QByteArray toBase64(QByteArray::Base64Options options) const 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 const
QByteArray toUtf8() const const