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