cutelyst 4.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;
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
54void CredentialPassword::setPasswordField(const QString &fieldName)
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
78void CredentialPassword::setPasswordPreSalt(const QString &passwordPreSalt)
79{
81 d->passwordPreSalt = passwordPreSalt;
82}
83
85{
86 Q_D(const CredentialPassword);
87 return d->passwordPostSalt;
88}
89
90void CredentialPassword::setPasswordPostSalt(const QString &passwordPostSalt)
91{
93 d->passwordPostSalt = passwordPostSalt;
94}
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
132QByteArray CredentialPassword::createPassword(const QByteArray &password,
133 QCryptographicHash::Algorithm method,
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
155QByteArray CredentialPassword::createPassword(const QByteArray &password)
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
164QByteArray CredentialPassword::pbkdf2(QCryptographicHash::Algorithm method,
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
221QByteArray CredentialPassword::hmac(QCryptographicHash::Algorithm method,
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 const QString password = passwordPreSalt + authinfo.value(passwordField) + passwordPostSalt;
232 const QString storedPassword = user.value(passwordField).toString();
233
234 if (Q_LIKELY(passwordType == CredentialPassword::Hashed)) {
235 return CredentialPassword::validatePassword(password.toUtf8(), storedPassword.toUtf8());
236 } else if (passwordType == CredentialPassword::Clear) {
237 return storedPassword == password;
238 } else if (passwordType == CredentialPassword::None) {
239 qCDebug(C_CREDENTIALPASSWORD) << "CredentialPassword is set to ignore password check";
240 return true;
241 }
242
243 return false;
244}
245
246QByteArray CredentialPasswordPrivate::cryptoEnumToStr(QCryptographicHash::Algorithm method)
247{
248 QByteArray hashmethod;
249
250#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
251 if (method == QCryptographicHash::Md4) {
252 hashmethod = QByteArrayLiteral("Md4");
253 } else if (method == QCryptographicHash::Md5) {
254 hashmethod = QByteArrayLiteral("Md5");
255 }
256#endif
257 if (method == QCryptographicHash::Sha1) {
258 hashmethod = QByteArrayLiteral("Sha1");
259 }
260#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
261 if (method == QCryptographicHash::Sha224) {
262 hashmethod = QByteArrayLiteral("Sha224");
263 } else if (method == QCryptographicHash::Sha256) {
264 hashmethod = QByteArrayLiteral("Sha256");
265 } else if (method == QCryptographicHash::Sha384) {
266 hashmethod = QByteArrayLiteral("Sha384");
267 } else if (method == QCryptographicHash::Sha512) {
268 hashmethod = QByteArrayLiteral("Sha512");
269 } else if (method == QCryptographicHash::Sha3_224) {
270 hashmethod = QByteArrayLiteral("Sha3_224");
271 } else if (method == QCryptographicHash::Sha3_256) {
272 hashmethod = QByteArrayLiteral("Sha3_256");
273 } else if (method == QCryptographicHash::Sha3_384) {
274 hashmethod = QByteArrayLiteral("Sha3_384");
275 } else if (method == QCryptographicHash::Sha3_512) {
276 hashmethod = QByteArrayLiteral("Sha3_512");
277 }
278#endif
279
280 return hashmethod;
281}
282
283int CredentialPasswordPrivate::cryptoStrToEnum(const QByteArray &hashMethod)
284{
285 QByteArray hashmethod = hashMethod;
286
287 int method = -1;
288#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
289 if (hashmethod == "Md4") {
290 method = QCryptographicHash::Md4;
291 } else if (hashmethod == "Md5") {
292 method = QCryptographicHash::Md5;
293 }
294#endif
295 if (hashmethod == "Sha1") {
296 method = QCryptographicHash::Sha1;
297 }
298#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
299 if (hashmethod == "Sha224") {
300 method = QCryptographicHash::Sha224;
301 } else if (hashmethod == "Sha256") {
302 method = QCryptographicHash::Sha256;
303 } else if (hashmethod == "Sha384") {
304 method = QCryptographicHash::Sha384;
305 } else if (hashmethod == "Sha512") {
306 method = QCryptographicHash::Sha512;
307 } else if (hashmethod == "Sha3_224") {
308 method = QCryptographicHash::Sha3_224;
309 } else if (hashmethod == "Sha3_256") {
310 method = QCryptographicHash::Sha3_256;
311 } else if (hashmethod == "Sha3_384") {
312 method = QCryptographicHash::Sha3_384;
313 } else if (hashmethod == "Sha3_512") {
314 method = QCryptographicHash::Sha3_512;
315 }
316#endif
317
318 return method;
319}
320
321#include "moc_credentialpassword.cpp"
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:38
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.
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