Passwordfile library 5.2.0
C++ library to read/write passwords from/to encrypted files
Loading...
Searching...
No Matches
passwordfiletests.cpp
Go to the documentation of this file.
2#include "../io/entry.h"
4
5#include "./utils.h"
6
7#include <cppunit/TestFixture.h>
8#include <cppunit/extensions/HelperMacros.h>
9
10using namespace std;
11using namespace Io;
12using namespace CppUtilities;
13using namespace CppUtilities::Literals;
14using namespace CPPUNIT_NS;
15
19class PasswordFileTests : public TestFixture {
20 CPPUNIT_TEST_SUITE(PasswordFileTests);
21 CPPUNIT_TEST(testReading);
22 CPPUNIT_TEST(testBasicWriting);
23 CPPUNIT_TEST(testExtendedWriting);
24 CPPUNIT_TEST(testAuthTagVerification);
25 CPPUNIT_TEST_SUITE_END();
26
27public:
28 void setUp() override;
29 void tearDown() override;
30
31 void testReading();
32 void testReading(const string &context, const string &testfile1path, const string &testfile1password, const string &testfile2,
33 const string &testfile2password, bool testfile2Mod, bool extendedHeaderMod, bool withHMAC = false);
34 void testBasicWriting();
37};
38
40
44
48
53{
54 testReading("read", testFilePath("testfile1.pwmgr"), "123456", testFilePath("testfile2.pwmgr"), string(), false, false);
55}
56
57void PasswordFileTests::testReading(const string &context, const string &testfile1path, const string &testfile1password, const string &testfile2,
58 const string &testfile2password, bool testfilesMod, bool extendedHeaderMod, bool withHMAC)
59{
60 PasswordFile file;
61
62 // open testfile 1 ...
63 file.setPath(testfile1path);
65
66 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, !testfile1password.empty(), file.isEncryptionUsed());
67 // attempt to decrypt using a wrong password
68 file.setPassword(testfile1password + "asdf");
69 if (!testfile1password.empty()) {
70 CPPUNIT_ASSERT_THROW(file.load(), CryptoException);
71 }
72 // attempt to decrypt using the correct password
73 file.setPassword(testfile1password);
74 file.load();
75 // test root entry
76 const NodeEntry *const rootEntry = file.rootEntry();
77 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testfile1"s, rootEntry->label());
78 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 4_st, rootEntry->children().size());
79
80 // test testaccount1
81 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testaccount1"s, rootEntry->children()[0]->label());
82 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, EntryType::Account, rootEntry->children()[0]->type());
83 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "pin"s, static_cast<AccountEntry *>(rootEntry->children()[0])->fields().at(0).name());
84 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "123456"s, static_cast<AccountEntry *>(rootEntry->children()[0])->fields().at(0).value());
85 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, FieldType::Password, static_cast<AccountEntry *>(rootEntry->children()[0])->fields().at(0).type());
86 CPPUNIT_ASSERT(
87 static_cast<AccountEntry *>(rootEntry->children()[0])->fields().at(0).tiedAccount() == static_cast<AccountEntry *>(rootEntry->children()[0]));
88 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, FieldType::Normal, static_cast<AccountEntry *>(rootEntry->children()[0])->fields().at(1).type());
89 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 2_st, static_cast<AccountEntry *>(rootEntry->children()[0])->fields().size());
90
91 // test testaccount2
92 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testaccount2"s, rootEntry->children()[1]->label());
93 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, EntryType::Account, rootEntry->children()[1]->type());
94 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 0_st, static_cast<AccountEntry *>(rootEntry->children()[1])->fields().size());
95
96 // test testcategory1
97 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testcategory1"s, rootEntry->children()[2]->label());
98 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, EntryType::Node, rootEntry->children()[2]->type());
99 const NodeEntry *const category = static_cast<NodeEntry *>(rootEntry->children()[2]);
100 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 3_st, category->children().size());
101 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, EntryType::Node, category->children()[2]->type());
102 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 2_st, static_cast<NodeEntry *>(category->children()[2])->children().size());
103
104 // test testaccount3
105 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testaccount3"s, rootEntry->children()[3]->label());
106
107 if (!testfilesMod) {
108 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption, compression"s, flagsToString(file.saveOptions()));
109 } else if (extendedHeaderMod) {
110 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "foo"s, file.extendedHeader());
111 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption, password hashing"s, flagsToString(file.saveOptions()));
112 } else {
113 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, ""s, file.extendedHeader());
114 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "compression"s, flagsToString(file.saveOptions()));
115 }
116 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, ""s, file.encryptedExtendedHeader());
117
118 // open testfile 2
119 file.setPath(testfile2);
121
122 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, !testfile2password.empty(), file.isEncryptionUsed());
123 file.setPassword(testfile2password);
124 file.load();
125 const NodeEntry *const rootEntry2 = file.rootEntry();
126 if (testfilesMod) {
127 if (withHMAC && extendedHeaderMod) {
128 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, static_cast<std::uint32_t>(6), file.version());
129 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption, password hashing, HMAC"s, flagsToString(file.saveOptions()));
130 } else if (withHMAC) {
131 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, static_cast<std::uint32_t>(4), file.version());
132 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption, HMAC"s, flagsToString(file.saveOptions()));
133 } else if (extendedHeaderMod) {
134 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, static_cast<std::uint32_t>(6), file.version());
135 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption, password hashing"s, flagsToString(file.saveOptions()));
136 } else {
137 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, static_cast<std::uint32_t>(3), file.version());
138 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "encryption"s, flagsToString(file.saveOptions()));
139 }
140 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testfile2 - modified"s, rootEntry2->label());
141 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 2_st, rootEntry2->children().size());
142 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "newAccount"s, rootEntry2->children()[1]->label());
143 } else {
144 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, static_cast<std::uint32_t>(3), file.version());
145 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "testfile2"s, rootEntry2->label());
146 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, 1_st, rootEntry2->children().size());
147 }
148 if (extendedHeaderMod) {
149 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "foo"s, file.extendedHeader());
150 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, "bar"s, file.encryptedExtendedHeader());
151
152 } else {
153 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, ""s, file.extendedHeader());
154 CPPUNIT_ASSERT_EQUAL_MESSAGE(context, ""s, file.encryptedExtendedHeader());
155 }
156}
157
162{
163 const string testfile1 = workingCopyPath("testfile1.pwmgr");
164 const string testfile2 = workingCopyPath("testfile2.pwmgr");
165 PasswordFile file;
166
167 // resave testfile 1
168 file.setPath(testfile1);
169 file.open();
170 file.setPassword("123456");
171 file.load();
172 file.doBackup();
174
175 // resave testfile 2
176 file.setPath(testfile2);
177 file.open();
178 file.load();
179 file.rootEntry()->setLabel("testfile2 - modified");
180 new AccountEntry("newAccount", file.rootEntry());
181 file.setPassword("654321");
182 file.doBackup();
184
185 // check results using the reading test
186 testReading("basic writing", testfile1, string(), testfile2, "654321", true, false, true);
187
188 // check backup files
189 testReading("basic writing", testfile1 + ".backup", "123456", testfile2 + ".backup", string(), false, false);
190}
191
196{
197 const string testfile1 = workingCopyPath("testfile1.pwmgr");
198 const string testfile2 = workingCopyPath("testfile2.pwmgr");
199 PasswordFile file;
200
201 // resave testfile 1
202 file.setPath(testfile1);
203 file.open();
204 file.setPassword("123456");
205 file.load();
206 CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
207 CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
208 file.doBackup();
209 file.extendedHeader() = "foo";
211
212 // resave testfile 2
213 file.setPath(testfile2);
214 file.open();
215 file.load();
216 CPPUNIT_ASSERT_EQUAL(""s, file.extendedHeader());
217 CPPUNIT_ASSERT_EQUAL(""s, file.encryptedExtendedHeader());
218 file.rootEntry()->setLabel("testfile2 - modified");
219 auto *const newAccount = new AccountEntry("newAccount", file.rootEntry());
220 file.setPassword("654321");
221 file.extendedHeader() = "foo";
222 file.encryptedExtendedHeader() = "bar";
223 file.doBackup();
225
226 // check results using the reading test
227 testReading("extended writing", testfile1, "123456", testfile2, "654321", true, true, true);
228
229 // check backup files
230 testReading("extended writing", testfile1 + ".backup", "123456", testfile2 + ".backup", string(), false, false);
231
232 // remove newAccount again to check what happens if the file size decreases
233 const auto fileSize = file.size();
234 delete newAccount;
236 file.close();
237 file.clearEntries();
238 file.open();
239 CPPUNIT_ASSERT_LESS(fileSize, file.size());
240 file.load();
241
242 auto path = std::list<std::string>{ "newAccount" };
243 CPPUNIT_ASSERT(file.rootEntry());
244 CPPUNIT_ASSERT(!file.rootEntry()->entryByPath(path));
245}
246
248{
249 auto file = PasswordFile(workingCopyPath("testfile3-hmac.pwmgr"), "123456");
250 file.open();
251 file.load();
252 CPPUNIT_ASSERT_MESSAGE("auth tag present", file.saveOptions() & PasswordFileSaveFlags::AuthenticationTag);
253 CPPUNIT_ASSERT_EQUAL_MESSAGE("auth tag not returned as extended data", ""s, file.extendedHeader());
254
255 // corrupt file and try reloading it
256 file.fileStream().seekp(0x78);
257 file.fileStream().put(0x00);
258 file.close();
259 file.clearEntries();
260 file.open();
261 auto error = std::string();
262 try {
263 file.load();
264 } catch (const CryptoException &e) {
265 error = e.what();
266 }
267 CPPUNIT_ASSERT_EQUAL_MESSAGE("auth failed", "Authentication failed: data integrity check failed (wrong password or file corrupted)."s, error);
268}
The exception that is thrown when a parsing error occurs.
Definition entry.h:170
const std::vector< Field > & fields() const
Definition entry.h:194
The exception that is thrown when an encryption/decryption error occurs.
void setLabel(const std::string &label)
Sets the label.
Definition entry.h:80
const std::string & label() const
Returns the label.
Definition entry.h:70
The NodeEntry class acts as parent for other entries.
Definition entry.h:114
Entry * entryByPath(std::list< std::string > &path, bool includeThis=true, const EntryType *creationType=nullptr)
Returns an entry specified by the provided path.
Definition entry.cpp:347
const std::vector< Entry * > & children() const
Definition entry.h:145
The PasswordFile class holds account information in the form of Entry and Field instances and provide...
const NodeEntry * rootEntry() const
Returns the root entry if present or nullptr otherwise.
void clearEntries()
Removes the root element if one is present.
void open(PasswordFileOpenFlags options=PasswordFileOpenFlags::Default)
Opens the file.
void load()
Reads the contents of the file.
void close()
Closes the file if currently opened.
std::uint32_t version() const
Returns the file version used the last time when saving the file (the version of the file as it is on...
void setPassword(const std::string &password)
Sets the current password.
bool isEncryptionUsed()
Returns an indication whether encryption is used and the file is open; returns always false otherwise...
std::size_t size()
Returns the size of the file if the file is open; otherwise returns zero.
void save(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file under path() replacing its previous contents.
std::string & encryptedExtendedHeader()
Returns the encrypted extended header.
void setPath(const std::string &value)
Sets the current file path.
std::string & extendedHeader()
Returns the extended header.
void doBackup()
Creates a backup of the file.
PasswordFileSaveFlags saveOptions() const
Returns the save options used the last time when saving the file.
The PasswordFileTests class tests the Io::PasswordFile class.
void setUp() override
void testBasicWriting()
Tests writing (and reading again) using basic features.
void testExtendedWriting()
Tests writing (and reading again) using extended features.
void testReading()
Tests reading the testfiles testfile{1,2}.pwmgr.
void tearDown() override
Contains all IO related classes.
PASSWORD_FILE_EXPORT std::string flagsToString(PasswordFileOpenFlags flags)
Returns a comma-separated string for the specified flags.
CPPUNIT_TEST_SUITE_REGISTRATION(PasswordFileTests)