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
13using namespace Cutelyst;
14
15Q_LOGGING_CATEGORY(C_CREDENTIALPASSWORD, "cutelyst.plugin.credentialpassword", QtWarningMsg)
16
19 , d_ptr(new CredentialPasswordPrivate)
20{
21}
22
23CredentialPassword::~CredentialPassword()
24{
25 delete d_ptr;
26}
27
30 const ParamsMultiMap &authinfo)
31{
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{
57 d->passwordField = fieldName;
58}
59
60CredentialPassword::PasswordType CredentialPassword::passwordType() const
61{
62 Q_D(const CredentialPassword);
63 return d->passwordType;
64}
65
66void CredentialPassword::setPasswordType(Cutelyst::CredentialPassword::PasswordType type)
67{
69 d->passwordType = type;
70}
71
73{
74 Q_D(const CredentialPassword);
75 return d->passwordPreSalt;
76}
77
83
85{
86 Q_D(const CredentialPassword);
87 return d->passwordPostSalt;
88}
89
95
96// To avoid timming attack
97bool 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
111bool 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
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
228bool 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
254QByteArray 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
291int 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") {
299 } else if (hashmethod == "Md5") {
301 }
302#endif
303 if (hashmethod == "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"
AuthenticationCredential(QObject *parent=nullptr)
Constructs a new AuthenticationCredential object with the given parent.
virtual AuthenticationUser findUser(Context *c, const ParamsMultiMap &userinfo)
Tries to find the user with authinfo returning a non null AuthenticationUser on success.
bool isNull() const
Returns true if the object is null.
The Cutelyst Context.
Definition context.h:39
void setPasswordType(PasswordType type)
Sets the type of password this class will be dealing with.
QString passwordField() const
Returns the field to look for when authenticating the user.
void setPasswordPostSalt(const QString &passwordPostSalt)
Sets the salt string to be appended to the password.
CredentialPassword(QObject *parent=nullptr)
Constructs a new CredentialPassword object with the given parent.
AuthenticationUser authenticate(Context *c, AuthenticationRealm *realm, const ParamsMultiMap &authinfo) final
Tries to authenticate the authinfo using the give realm.
static QByteArray pbkdf2(QCryptographicHash::Algorithm method, const QByteArray &password, const QByteArray &salt, int rounds, int keyLength)
Generates a pbkdf2 string for the given password.
static bool validatePassword(const QByteArray &password, const QByteArray &correctHash)
Validates the given password against the correct hash.
QString passwordPreSalt() const
Returns the salt string to be prepended to the password.
PasswordType passwordType() const
Returns the type of password this class will be dealing with.
QString passwordPostSalt() const
Returns the salt string to be appended to the password.
static QByteArray createPassword(const QByteArray &password, QCryptographicHash::Algorithm method, int iterations, int saltByteSize, int hashByteSize)
Creates a password hash string.
void setPasswordField(const QString &fieldName)
Sets the field to look for when authenticating the user.
static QByteArray hmac(QCryptographicHash::Algorithm method, const QByteArray &key, const QByteArray &message)
Generates the Hash-based message authentication code.
void setPasswordPreSalt(const QString &passwordPreSalt)
Sets the salt string to be prepended to the password.
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap
QByteArray & append(char ch)
iterator begin()
const_iterator cbegin() const const
const_iterator cend() const const
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
int length() const const
QByteArray number(int n, int base)
void reserve(int size)
void resize(int size)
int size() const const
QList< QByteArray > split(char sep) const const
QByteArray toBase64(Base64Options options) const const
void truncate(int pos)
virtual bool open(OpenMode mode) override
qint64 read(char *data, qint64 maxSize)
const T & at(int i) const const
int size() const const
void addData(const char *data, int length)
QByteArray hash(const QByteArray &message, const QByteArray &key, QCryptographicHash::Algorithm method)
QByteArray result() const const
QObject(QObject *parent)
QObject * parent() const const
QString & append(QChar ch)
QString & prepend(QChar ch)
QByteArray toUtf8() const const
QUuid createUuid()
QByteArray toRfc4122() const const
QString toString() const const