cutelyst 5.0.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
13using namespace Cutelyst;
14using namespace Qt::StringLiterals;
15
16Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
17
20 , d_ptr(new CredentialPasswordPrivate)
21{
22}
23
25{
26 delete d_ptr;
27}
28
31 const ParamsMultiMap &authinfo)
32{
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{
58 d->passwordField = fieldName;
59}
60
62{
63 Q_D(const CredentialPassword);
64 return d->passwordType;
65}
66
72
74{
75 Q_D(const CredentialPassword);
76 return d->passwordPreSalt;
77}
78
80{
82 d->passwordPreSalt = passwordPreSalt;
83}
84
86{
87 Q_D(const CredentialPassword);
88 return d->passwordPostSalt;
89}
90
92{
94 d->passwordPostSalt = passwordPostSalt;
95}
96
97// To avoid timming attack
98bool 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
107namespace {
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
115bool 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
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
233bool 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
251QByteArray 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
288int 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") {
296 } else if (hashmethod == "Md5") {
298 }
299#endif
300 if (hashmethod == "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"
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)
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)
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