TestPasswordGenerator.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*
  2. * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
  3. *
  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.
  8. *
  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.
  13. *
  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/>.
  16. */
  17. #include "TestPasswordGenerator.h"
  18. #include "crypto/Crypto.h"
  19. #include <QRegularExpression>
  20. #include <QTest>
  21. QTEST_GUILESS_MAIN(TestPasswordGenerator)
  22. Q_DECLARE_METATYPE(PasswordGenerator::CharClasses)
  23. Q_DECLARE_METATYPE(PasswordGenerator::GeneratorFlags)
  24. namespace
  25. {
  26. PasswordGenerator::CharClasses to_flags(PasswordGenerator::CharClass x)
  27. {
  28. return x;
  29. }
  30. PasswordGenerator::GeneratorFlags to_flags(PasswordGenerator::GeneratorFlag x)
  31. {
  32. return x;
  33. }
  34. } // namespace
  35. void TestPasswordGenerator::initTestCase()
  36. {
  37. QVERIFY(Crypto::init());
  38. }
  39. void TestPasswordGenerator::init()
  40. {
  41. m_generator.reset();
  42. }
  43. void TestPasswordGenerator::testCustomCharacterSet_data()
  44. {
  45. QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
  46. QTest::addColumn<QString>("customCharacterSet");
  47. QTest::addColumn<QRegularExpression>("expected");
  48. QTest::addRow("With active classes") << to_flags(PasswordGenerator::CharClass::UpperLetters) << "abc"
  49. << QRegularExpression("^[abcA-Z]{2000}$");
  50. QTest::addRow("Without any active class")
  51. << to_flags(PasswordGenerator::CharClass::NoClass) << "abc" << QRegularExpression("^[abc]{2000}$");
  52. }
  53. void TestPasswordGenerator::testCustomCharacterSet()
  54. {
  55. QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
  56. QFETCH(QString, customCharacterSet);
  57. QFETCH(QRegularExpression, expected);
  58. m_generator.setCharClasses(activeCharacterClasses);
  59. m_generator.setCustomCharacterSet(customCharacterSet);
  60. m_generator.setLength(2000);
  61. QVERIFY(m_generator.isValid());
  62. QString password = m_generator.generatePassword();
  63. QCOMPARE(password.size(), 2000);
  64. QVERIFY(expected.match(password).hasMatch());
  65. }
  66. void TestPasswordGenerator::testCharClasses_data()
  67. {
  68. QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
  69. QTest::addColumn<QRegularExpression>("expected");
  70. QTest::addRow("Lower Letters") << to_flags(PasswordGenerator::CharClass::LowerLetters)
  71. << QRegularExpression(R"(^[a-z]{2000}$)");
  72. QTest::addRow("Upper Letters") << to_flags(PasswordGenerator::CharClass::UpperLetters)
  73. << QRegularExpression(R"(^[A-Z]{2000}$)");
  74. QTest::addRow("Numbers") << to_flags(PasswordGenerator::CharClass::Numbers) << QRegularExpression(R"(^\d{2000}$)");
  75. QTest::addRow("Braces") << to_flags(PasswordGenerator::CharClass::Braces)
  76. << QRegularExpression(R"(^[\(\)\[\]\{\}]{2000}$)");
  77. QTest::addRow("Punctuation") << to_flags(PasswordGenerator::CharClass::Punctuation)
  78. << QRegularExpression(R"(^[\.,:;]{2000}$)");
  79. QTest::addRow("Quotes") << to_flags(PasswordGenerator::CharClass::Quotes) << QRegularExpression(R"(^["']{2000}$)");
  80. QTest::addRow("Dashes") << to_flags(PasswordGenerator::CharClass::Dashes)
  81. << QRegularExpression(R"(^[\-/\\_|]{2000}$)");
  82. QTest::addRow("Math") << to_flags(PasswordGenerator::CharClass::Math) << QRegularExpression(R"(^[!\*\+\-<=>\?]+$)");
  83. QTest::addRow("Logograms") << to_flags(PasswordGenerator::CharClass::Logograms)
  84. << QRegularExpression(R"(^[#`~%&^$@]{2000}$)");
  85. QTest::addRow("Extended ASCII") << to_flags(PasswordGenerator::CharClass::EASCII)
  86. << QRegularExpression(R"(^[^a-zA-Z0-9\.,:;"'\-/\\_|!\*\+\-<=>\?#`~%&^$@]{2000}$)");
  87. QTest::addRow("Combinations 1") << (PasswordGenerator::CharClass::LowerLetters
  88. | PasswordGenerator::CharClass::UpperLetters
  89. | PasswordGenerator::CharClass::Braces)
  90. << QRegularExpression(R"(^[a-zA-Z\(\)\[\]\{\}]{2000}$)");
  91. QTest::addRow("Combinations 2") << (PasswordGenerator::CharClass::Quotes | PasswordGenerator::CharClass::Numbers
  92. | PasswordGenerator::CharClass::Dashes)
  93. << QRegularExpression(R"(^["'\d\-/\\_|]{2000}$)");
  94. }
  95. void TestPasswordGenerator::testCharClasses()
  96. {
  97. QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
  98. QFETCH(QRegularExpression, expected);
  99. m_generator.setCharClasses(activeCharacterClasses);
  100. m_generator.setLength(2000);
  101. QVERIFY(m_generator.isValid());
  102. QString password = m_generator.generatePassword();
  103. QCOMPARE(password.size(), 2000);
  104. QVERIFY(expected.match(password).hasMatch());
  105. }
  106. void TestPasswordGenerator::testLookalikeExclusion_data()
  107. {
  108. QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
  109. QTest::addColumn<QRegularExpression>("expected");
  110. QTest::addRow("Upper Letters") << (PasswordGenerator::CharClass::LowerLetters
  111. | PasswordGenerator::CharClass::UpperLetters)
  112. << QRegularExpression("^[^lBGIO]{2000}$");
  113. QTest::addRow("Letters and Numbers") << (PasswordGenerator::CharClass::LowerLetters
  114. | PasswordGenerator::CharClass::UpperLetters
  115. | PasswordGenerator::CharClass::Numbers)
  116. << QRegularExpression("^[^lBGIO0168]{2000}$");
  117. QTest::addRow("Letters, Numbers and extended ASCII")
  118. << (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
  119. | PasswordGenerator::CharClass::Numbers | PasswordGenerator::CharClass::EASCII)
  120. << QRegularExpression("^[^lBGIO0168﹒]{2000}$");
  121. }
  122. void TestPasswordGenerator::testLookalikeExclusion()
  123. {
  124. QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
  125. QFETCH(QRegularExpression, expected);
  126. m_generator.setFlags(PasswordGenerator::ExcludeLookAlike);
  127. m_generator.setCharClasses(activeCharacterClasses);
  128. m_generator.setLength(2000);
  129. QVERIFY(m_generator.isValid());
  130. QString password = m_generator.generatePassword();
  131. QCOMPARE(password.size(), 2000);
  132. QVERIFY(expected.match(password).hasMatch());
  133. }
  134. void TestPasswordGenerator::testValidity_data()
  135. {
  136. QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
  137. QTest::addColumn<PasswordGenerator::GeneratorFlags>("generatorFlags");
  138. QTest::addColumn<QString>("customCharacterSet");
  139. QTest::addColumn<QString>("excludedCharacters");
  140. QTest::addColumn<int>("length");
  141. QTest::addColumn<bool>("isValid");
  142. QTest::addRow("No active class") << to_flags(PasswordGenerator::CharClass::NoClass)
  143. << PasswordGenerator::GeneratorFlags() << QString() << QString()
  144. << PasswordGenerator::DefaultLength << false;
  145. QTest::addRow("0 length") << to_flags(PasswordGenerator::CharClass::DefaultCharset)
  146. << PasswordGenerator::GeneratorFlags() << QString() << QString() << 0 << false;
  147. QTest::addRow("All active classes excluded")
  148. << to_flags(PasswordGenerator::CharClass::Numbers) << PasswordGenerator::GeneratorFlags() << QString()
  149. << QString("0123456789") << PasswordGenerator::DefaultLength << false;
  150. QTest::addRow("All active classes excluded")
  151. << to_flags(PasswordGenerator::CharClass::NoClass) << PasswordGenerator::GeneratorFlags() << QString()
  152. << QString("0123456789") << PasswordGenerator::DefaultLength << false;
  153. QTest::addRow("One from every class with too few classes")
  154. << (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters)
  155. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString() << 1 << false;
  156. QTest::addRow("One from every class with excluded classes")
  157. << (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
  158. | PasswordGenerator::CharClass::Numbers)
  159. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString("0123456789") << 2
  160. << true;
  161. QTest::addRow("Defaults valid") << to_flags(PasswordGenerator::CharClass::DefaultCharset)
  162. << to_flags(PasswordGenerator::GeneratorFlag::DefaultFlags)
  163. << PasswordGenerator::DefaultCustomCharacterSet
  164. << PasswordGenerator::DefaultExcludedChars << PasswordGenerator::DefaultLength
  165. << true;
  166. QTest::addRow("No active classes but custom charset")
  167. << to_flags(PasswordGenerator::CharClass::NoClass) << to_flags(PasswordGenerator::GeneratorFlag::DefaultFlags)
  168. << QString("a") << QString() << 1 << true;
  169. }
  170. void TestPasswordGenerator::testValidity()
  171. {
  172. QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
  173. QFETCH(PasswordGenerator::GeneratorFlags, generatorFlags);
  174. QFETCH(QString, customCharacterSet);
  175. QFETCH(QString, excludedCharacters);
  176. QFETCH(int, length);
  177. QFETCH(bool, isValid);
  178. m_generator.setCharClasses(activeCharacterClasses);
  179. m_generator.setFlags(generatorFlags);
  180. m_generator.setCustomCharacterSet(customCharacterSet);
  181. m_generator.setExcludedCharacterSet(excludedCharacters);
  182. m_generator.setLength(length);
  183. QCOMPARE(m_generator.isValid(), isValid);
  184. }
  185. void TestPasswordGenerator::testMinLength_data()
  186. {
  187. QTest::addColumn<PasswordGenerator::CharClasses>("activeCharacterClasses");
  188. QTest::addColumn<PasswordGenerator::GeneratorFlags>("generatorFlags");
  189. QTest::addColumn<QString>("customCharacterSet");
  190. QTest::addColumn<QString>("excludedCharacters");
  191. QTest::addColumn<int>("expectedMinLength");
  192. QTest::addRow("No restriction without charsFromEveryGroup")
  193. << to_flags(PasswordGenerator::CharClass::Numbers)
  194. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup)
  195. << PasswordGenerator::DefaultCustomCharacterSet << PasswordGenerator::DefaultExcludedChars << 1;
  196. QTest::addRow("Min length should equal number of active classes")
  197. << (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
  198. | PasswordGenerator::CharClass::Numbers)
  199. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString() << 3;
  200. QTest::addRow("Classes fully excluded by excluded characters do not count towards min length")
  201. << (PasswordGenerator::CharClass::Numbers | PasswordGenerator::LowerLetters
  202. | PasswordGenerator::CharClass::UpperLetters)
  203. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString() << QString("0123456789") << 2;
  204. QTest::addRow("Custom charset counts as class")
  205. << to_flags(PasswordGenerator::CharClass::UpperLetters)
  206. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString("a") << QString() << 2;
  207. QTest::addRow("Custom characters count even if included by an active class already")
  208. << (PasswordGenerator::CharClass::LowerLetters | PasswordGenerator::CharClass::UpperLetters
  209. | PasswordGenerator::CharClass::Numbers)
  210. << to_flags(PasswordGenerator::GeneratorFlag::CharFromEveryGroup) << QString("012345") << QString() << 4;
  211. }
  212. void TestPasswordGenerator::testMinLength()
  213. {
  214. QFETCH(PasswordGenerator::CharClasses, activeCharacterClasses);
  215. QFETCH(PasswordGenerator::GeneratorFlags, generatorFlags);
  216. QFETCH(QString, customCharacterSet);
  217. QFETCH(QString, excludedCharacters);
  218. QFETCH(int, expectedMinLength);
  219. m_generator.setCharClasses(activeCharacterClasses);
  220. m_generator.setFlags(generatorFlags);
  221. m_generator.setCustomCharacterSet(customCharacterSet);
  222. m_generator.setExcludedCharacterSet(excludedCharacters);
  223. QCOMPARE(m_generator.getMinLength(), expectedMinLength);
  224. }
  225. void TestPasswordGenerator::testReset()
  226. {
  227. PasswordGenerator default_generator;
  228. // Modify generator
  229. m_generator.setCharClasses(PasswordGenerator::CharClass::NoClass);
  230. m_generator.setFlags(PasswordGenerator::GeneratorFlag::NoFlags);
  231. m_generator.setCustomCharacterSet("avc");
  232. m_generator.setExcludedCharacterSet("asdv");
  233. m_generator.setLength(m_generator.getLength() + 1);
  234. Q_ASSERT(m_generator.getActiveClasses() != default_generator.getActiveClasses());
  235. Q_ASSERT(m_generator.getFlags() != default_generator.getFlags());
  236. Q_ASSERT(m_generator.getCustomCharacterSet() != default_generator.getCustomCharacterSet());
  237. Q_ASSERT(m_generator.getExcludedCharacterSet() != default_generator.getExcludedCharacterSet());
  238. m_generator.reset();
  239. QCOMPARE(m_generator.getActiveClasses(), default_generator.getActiveClasses());
  240. QCOMPARE(m_generator.getFlags(), default_generator.getFlags());
  241. QCOMPARE(m_generator.getCustomCharacterSet(), default_generator.getCustomCharacterSet());
  242. QCOMPARE(m_generator.getExcludedCharacterSet(), default_generator.getExcludedCharacterSet());
  243. QCOMPARE(m_generator.getLength(), default_generator.getLength());
  244. }