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
14using namespace Cutelyst;
15using namespace Qt::StringLiterals;
16
17Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
18
21 , d_ptr(new CredentialPasswordPrivate)
22{
23}
24
26{
27 delete d_ptr;
28}
29
32 const ParamsMultiMap &authinfo)
33{
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{
68 d->passwordField = fieldName;
69}
70
72{
73 Q_D(const CredentialPassword);
74 return d->passwordType;
75}
76
82
84{
85 Q_D(const CredentialPassword);
86 return d->passwordPreSalt;
87}
88
90{
92 d->passwordPreSalt = passwordPreSalt;
93}
94
96{
97 Q_D(const CredentialPassword);
98 return d->passwordPostSalt;
99}
100
102{
104 d->passwordPostSalt = passwordPostSalt;
105}
106
107// To avoid timming attack
108bool 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
117namespace {
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
125bool 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
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
243bool 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
264QByteArray 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
301int 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") {
309 } else if (hashmethod == "Md5") {
311 }
312#endif
313 if (hashmethod == "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"
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
bool checkPassword(Context *c, AuthenticationRealm *realm, const QString &password, const QString &passwordField=QStringLiteral("password"))
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