2
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation, either version 2 or (at your option)
7
* version 3 of the License.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18
#include "TestPasswordGenerator.h"
19
#include "crypto/Crypto.h"
21
#include <QRegularExpression>
24
QTEST_GUILESS_MAIN(TestPasswordGenerator)
26
Q_DECLARE_METATYPE(PasswordGenerator::CharClasses)
27
Q_DECLARE_METATYPE(PasswordGenerator::GeneratorFlags)
31
PasswordGenerator::CharClasses to_flags(PasswordGenerator::CharClass x)
36
PasswordGenerator::GeneratorFlags to_flags(PasswordGenerator::GeneratorFlag x)
42
void TestPasswordGenerator::initTestCase()
44
QVERIFY(Crypto::init());
47
void TestPasswordGenerator::init()
52
void TestPasswordGenerator::testCustomCharacterSet_data()
54
QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
55
QTest::addColumn<QString>("customCharacterSet");
56
QTest::addColumn<QRegularExpression>("expected");
58
QTest::addRow("With active classes") << to_flags(PasswordGenerator::CharClass::UpperLetters) << "abc"
59
<< QRegularExpression("^[abcA-Z]{2000}$");
60
QTest::addRow("Without any active class")
61
<< to_flags(PasswordGenerator::CharClass::NoClass) << "abc" << QRegularExpression("^[abc]{2000}$");
64
void TestPasswordGenerator::testCustomCharacterSet()
66
QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
67
QFETCH(QString, customCharacterSet);
68
QFETCH(QRegularExpression, expected);
70
m_generator.setCharClasses(activeCharacterClasses);
71
m_generator.setCustomCharacterSet(customCharacterSet);
72
m_generator.setLength(2000);
74
QVERIFY(m_generator.isValid());
75
QString password = m_generator.generatePassword();
76
QCOMPARE(password.size(), 2000);
77
QVERIFY(expected.match(password).hasMatch());
80
void TestPasswordGenerator::testCharClasses_data()
82
QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
83
QTest::addColumn<QRegularExpression>("expected");
85
QTest::addRow("Lower Letters") << to_flags(PasswordGenerator::CharClass::LowerLetters)
86
<< QRegularExpression(R"(^[a-z]{2000}$)");
87
QTest::addRow("Upper Letters") << to_flags(PasswordGenerator::CharClass::UpperLetters)
88
<< QRegularExpression(R"(^[A-Z]{2000}$)");
89
QTest::addRow("Numbers") << to_flags(PasswordGenerator::CharClass::Numbers) << QRegularExpression(R"(^\d{2000}$)");
90
QTest::addRow("Braces") << to_flags(PasswordGenerator::CharClass::Braces)
91
<< QRegularExpression(R"(^[\(\)\[\]\{\}]{2000}$)");
92
QTest::addRow("Punctuation") << to_flags(PasswordGenerator::CharClass::Punctuation)
93
<< QRegularExpression(R"(^[\.,:;]{2000}$)");
94
QTest::addRow("Quotes") << to_flags(PasswordGenerator::CharClass::Quotes) << QRegularExpression(R"(^["']{2000}$)");
95
QTest::addRow("Dashes") << to_flags(PasswordGenerator::CharClass::Dashes)
96
<< QRegularExpression(R"(^[\-/\\_|]{2000}$)");
97
QTest::addRow("Math") << to_flags(PasswordGenerator::CharClass::Math) << QRegularExpression(R"(^[!\*\+\-<=>\?]+$)");
98
QTest::addRow("Logograms") << to_flags(PasswordGenerator::CharClass::Logograms)
99
<< QRegularExpression(R"(^[#`~%&^$@]{2000}$)");
100
QTest::addRow("Extended ASCII") << to_flags(PasswordGenerator::CharClass::EASCII)
101
<< QRegularExpression(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]{2000}$)");
102
QTest::addRow("Combinations 1") << (PasswordGenerator::CharClass::LowerLetters
103
| PasswordGenerator::CharClass::UpperLetters
104
| PasswordGenerator::CharClass::Braces)
105
<< QRegularExpression(R"(^[a-zA-Z\(\)\[\]\{\}]{2000}$)");
106
QTest::addRow("Combinations 2") << (PasswordGenerator::CharClass::Quotes | PasswordGenerator::CharClass::Numbers
107
| PasswordGenerator::CharClass::Dashes)
108
<< QRegularExpression(R"(^["'\d\-/\\_|]{2000}$)");
111
void TestPasswordGenerator::testCharClasses()
114
QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
115
QFETCH(QRegularExpression, expected);
117
m_generator.setCharClasses(activeCharacterClasses);
118
m_generator.setLength(2000);
120
QVERIFY(m_generator.isValid());
121
QString password = m_generator.generatePassword();
122
QCOMPARE(password.size(), 2000);
123
QVERIFY(expected.match(password).hasMatch());
126
void TestPasswordGenerator::testLookalikeExclusion_data()
128
QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
129
QTest::addColumn<QRegularExpression>("expected");
130
QTest::addRow("Upper Letters") << (PasswordGenerator::CharClass::LowerLetters
131
| PasswordGenerator::CharClass::UpperLetters)
132
<< QRegularExpression("^[^lBGIO]{2000}$");
134
QTest::addRow("Letters and Numbers") << (PasswordGenerator::CharClass::LowerLetters
135
| PasswordGenerator::CharClass::UpperLetters
136
| PasswordGenerator::CharClass::Numbers)
137
<< QRegularExpression("^[^lBGIO0168]{2000}$");
139
QTest::addRow("Letters, Numbers and extended ASCII")
140
<< (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
141
| PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::EASCII)
142
<< QRegularExpression("^[^lBGIO0168﹒]{2000}$");
145
void TestPasswordGenerator::testLookalikeExclusion()
147
QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
148
QFETCH(QRegularExpression, expected);
150
m_generator.setFlags(PasswordGenerator::ExcludeLookAlike);
151
m_generator.setCharClasses(activeCharacterClasses);
152
m_generator.setLength(2000);
154
QVERIFY(m_generator.isValid());
155
QString password = m_generator.generatePassword();
156
QCOMPARE(password.size(), 2000);
157
QVERIFY(expected.match(password).hasMatch());
160
void TestPasswordGenerator::testValidity_data()
162
QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
163
QTest::addColumn<PasswordGenerator::GeneratorFlags>("generatorFlags");
164
QTest::addColumn<QString>("customCharacterSet");
165
QTest::addColumn<QString>("excludedCharacters");
166
QTest::addColumn<int>("length");
167
QTest::addColumn<bool>("isValid");
169
QTest::addRow("No active class") << to_flags(PasswordGenerator::CharClass::NoClass)
170
<< PasswordGenerator::GeneratorFlags() << QString() << QString()
171
<< PasswordGenerator::DefaultLength << false;
172
QTest::addRow("0 length") << to_flags(PasswordGenerator::CharClass::DefaultCharset)
173
<< PasswordGenerator::GeneratorFlags() << QString() << QString() << 0 << false;
174
QTest::addRow("All active classes excluded")
175
<< to_flags(PasswordGenerator::CharClass::Numbers) << PasswordGenerator::GeneratorFlags() << QString()
176
<< QString("0123456789") << PasswordGenerator::DefaultLength << false;
177
QTest::addRow("All active classes excluded")
178
<< to_flags(PasswordGenerator::CharClass::NoClass) << PasswordGenerator::GeneratorFlags() << QString()
179
<< QString("0123456789") << PasswordGenerator::DefaultLength << false;
180
QTest::addRow("One from every class with too few classes")
181
<< (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters)
182
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString() << 1 << false;
183
QTest::addRow("One from every class with excluded classes")
184
<< (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
185
| PasswordGenerator::CharClass::Numbers)
186
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString("0123456789") << 2
188
QTest::addRow("Defaults valid") << to_flags(PasswordGenerator::CharClass::DefaultCharset)
189
<< to_flags(PasswordGenerator::GeneratorFlag::DefaultFlags)
190
<< PasswordGenerator::DefaultCustomCharacterSet
191
<< PasswordGenerator::DefaultExcludedChars << PasswordGenerator::DefaultLength
193
QTest::addRow("No active classes but custom charset")
194
<< to_flags(PasswordGenerator::CharClass::NoClass) << to_flags(PasswordGenerator::GeneratorFlag::DefaultFlags)
195
<< QString("a") << QString() << 1 << true;
198
void TestPasswordGenerator::testValidity()
200
QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
201
QFETCH(PasswordGenerator::GeneratorFlags, generatorFlags);
202
QFETCH(QString, customCharacterSet);
203
QFETCH(QString, excludedCharacters);
205
QFETCH(bool, isValid);
207
m_generator.setCharClasses(activeCharacterClasses);
208
m_generator.setFlags(generatorFlags);
209
m_generator.setCustomCharacterSet(customCharacterSet);
210
m_generator.setExcludedCharacterSet(excludedCharacters);
211
m_generator.setLength(length);
212
QCOMPARE(m_generator.isValid(), isValid);
215
void TestPasswordGenerator::testMinLength_data()
217
QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
218
QTest::addColumn<PasswordGenerator::GeneratorFlags>("generatorFlags");
219
QTest::addColumn<QString>("customCharacterSet");
220
QTest::addColumn<QString>("excludedCharacters");
221
QTest::addColumn<int>("expectedMinLength");
223
QTest::addRow("No restriction without charsFromEveryGroup")
224
<< to_flags(PasswordGenerator::CharClass::Numbers)
225
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup)
226
<< PasswordGenerator::DefaultCustomCharacterSet << PasswordGenerator::DefaultExcludedChars << 1;
228
QTest::addRow("Min length should equal number of active classes")
229
<< (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
230
| PasswordGenerator::CharClass::Numbers)
231
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString() << 3;
232
QTest::addRow("Classes fully excluded by excluded characters do not count towards min length")
233
<< (PasswordGenerator::CharClass::Numbers | PasswordGenerator::LowerLetters
234
| PasswordGenerator::CharClass::UpperLetters)
235
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString("0123456789") << 2;
237
QTest::addRow("Custom charset counts as class")
238
<< to_flags(PasswordGenerator::CharClass::UpperLetters)
239
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString("a") << QString() << 2;
240
QTest::addRow("Custom characters count even if included by an active class already")
241
<< (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
242
| PasswordGenerator::CharClass::Numbers)
243
<< to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString("012345") << QString() << 4;
246
void TestPasswordGenerator::testMinLength()
248
QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
249
QFETCH(PasswordGenerator::GeneratorFlags, generatorFlags);
250
QFETCH(QString, customCharacterSet);
251
QFETCH(QString, excludedCharacters);
252
QFETCH(int, expectedMinLength);
254
m_generator.setCharClasses(activeCharacterClasses);
255
m_generator.setFlags(generatorFlags);
256
m_generator.setCustomCharacterSet(customCharacterSet);
257
m_generator.setExcludedCharacterSet(excludedCharacters);
258
QCOMPARE(m_generator.getMinLength(), expectedMinLength);
261
void TestPasswordGenerator::testReset()
263
PasswordGenerator default_generator;
266
m_generator.setCharClasses(PasswordGenerator::CharClass::NoClass);
267
m_generator.setFlags(PasswordGenerator::GeneratorFlag::NoFlags);
268
m_generator.setCustomCharacterSet("avc");
269
m_generator.setExcludedCharacterSet("asdv");
270
m_generator.setLength(m_generator.getLength() + 1);
272
Q_ASSERT(m_generator.getActiveClasses() != default_generator.getActiveClasses());
273
Q_ASSERT(m_generator.getFlags() != default_generator.getFlags());
274
Q_ASSERT(m_generator.getCustomCharacterSet() != default_generator.getCustomCharacterSet());
275
Q_ASSERT(m_generator.getExcludedCharacterSet() != default_generator.getExcludedCharacterSet());
278
QCOMPARE(m_generator.getActiveClasses(), default_generator.getActiveClasses());
279
QCOMPARE(m_generator.getFlags(), default_generator.getFlags());
280
QCOMPARE(m_generator.getCustomCharacterSet(), default_generator.getCustomCharacterSet());
281
QCOMPARE(m_generator.getExcludedCharacterSet(), default_generator.getExcludedCharacterSet());
282
QCOMPARE(m_generator.getLength(), default_generator.getLength());