6 #include <c++utilities/conversion/stringconversion.h> 7 #include <c++utilities/io/catchiofailure.h> 9 #include <openssl/conf.h> 10 #include <openssl/err.h> 11 #include <openssl/evp.h> 12 #include <openssl/rand.h> 39 PasswordFile::PasswordFile()
40 : m_freader(BinaryReader(&m_file))
41 , m_fwriter(BinaryWriter(&m_file))
43 m_file.exceptions(ios_base::failbit | ios_base::badbit);
51 : m_freader(BinaryReader(&m_file))
52 , m_fwriter(BinaryWriter(&m_file))
54 m_file.exceptions(ios_base::failbit | ios_base::badbit);
63 : m_path(other.m_path)
64 , m_rootEntry(other.m_rootEntry ? make_unique<
NodeEntry>(*other.m_rootEntry) : nullptr)
65 , m_extendedHeader(other.m_extendedHeader)
66 , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
67 , m_freader(BinaryReader(&m_file))
68 , m_fwriter(BinaryWriter(&m_file))
70 m_file.exceptions(ios_base::failbit | ios_base::badbit);
71 memcpy(m_password, other.m_password, 32);
78 : m_path(move(other.m_path))
79 , m_rootEntry(move(other.m_rootEntry))
80 , m_extendedHeader(move(other.m_extendedHeader))
81 , m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
82 , m_file(move(other.m_file))
83 , m_freader(BinaryReader(&m_file))
84 , m_fwriter(BinaryWriter(&m_file))
86 memcpy(m_password, other.m_password, 32);
103 if (m_path.empty()) {
104 throwIoFailure(
"Unable to open file because path is emtpy.");
106 m_file.open(m_path, readOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
117 m_file.seekg(0, ios_base::end);
118 if (m_file.tellg() == 0) {
119 throwIoFailure(
"File is empty.");
131 m_rootEntry.reset(
new NodeEntry(
"accounts"));
142 if (m_path.empty()) {
143 throwIoFailure(
"Unable to create file because path is empty.");
145 m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
158 if (!m_file.is_open()) {
164 if (m_freader.readUInt32LE() != 0x7770616DU) {
169 const auto version = m_freader.readUInt32LE();
170 if (version != 0x0U && version != 0x1U && version != 0x2U && version != 0x3U && version != 0x4U && version != 0x5U) {
173 bool decrypterUsed, ivUsed, compressionUsed;
174 if (version == 0x3U) {
175 const auto flags = m_freader.readByte();
176 decrypterUsed = flags & 0x80;
177 ivUsed = flags & 0x40;
178 compressionUsed = flags & 0x20;
180 decrypterUsed = version >= 0x1U;
181 ivUsed = version == 0x2U;
182 compressionUsed =
false;
188 if (version >= 0x4U) {
189 uint16 extendedHeaderSize = m_freader.readUInt16BE();
190 m_extendedHeader = m_freader.readString(extendedHeaderSize);
194 const auto headerSize =
static_cast<size_t>(m_file.tellg());
195 m_file.seekg(0, ios_base::end);
196 auto remainingSize =
static_cast<size_t>(m_file.tellg()) - headerSize;
197 m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
201 if (decrypterUsed && ivUsed) {
208 if (!remainingSize) {
213 vector<char> rawData;
214 m_freader.read(rawData, static_cast<streamoff>(remainingSize));
215 vector<char> decryptedData;
217 if (remainingSize > numeric_limits<int>::max()) {
222 EVP_CIPHER_CTX *ctx =
nullptr;
223 decryptedData.resize(remainingSize + 32);
224 int outlen1, outlen2;
225 if ((ctx = EVP_CIPHER_CTX_new()) ==
nullptr 226 || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
227 || EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
228 reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
230 || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
233 EVP_CIPHER_CTX_free(ctx);
236 auto errorCode = ERR_get_error();
241 msg += ERR_error_string(errorCode,
nullptr);
242 errorCode = ERR_get_error();
248 EVP_CIPHER_CTX_free(ctx);
250 const auto decryptedSize = outlen1 + outlen2;
251 if (decryptedSize < 0) {
254 remainingSize =
static_cast<size_t>(decryptedSize);
258 decryptedData.swap(rawData);
262 if (compressionUsed) {
263 if (remainingSize < 8) {
266 uLongf decompressedSize = ConversionUtilities::LE::toUInt64(decryptedData.data());
267 rawData.resize(decompressedSize);
269 reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8), remainingSize - 8)) {
271 throw ParsingException(
"Decompressing failed. The source buffer was too small.");
273 throw ParsingException(
"Decompressing failed. The destination buffer was too small.");
275 throw ParsingException(
"Decompressing failed. The input data was corrupted or incomplete.");
277 decryptedData.swap(rawData);
278 remainingSize = decompressedSize;
283 stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
284 decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(),
static_cast<streamsize
>(remainingSize));
285 if (version >= 0x5u) {
286 const auto extendedHeaderSize = m_freader.readUInt16BE();
287 m_encryptedExtendedHeader = m_freader.readString(extendedHeaderSize);
289 m_rootEntry.reset(
new NodeEntry(decryptedStream));
303 throw runtime_error(
"Root entry has not been created.");
307 if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
312 m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
315 write(useEncryption, useCompression);
330 throw runtime_error(
"Root entry has not been created.");
334 m_fwriter.writeUInt32LE(0x7770616DU);
337 m_fwriter.writeUInt32LE(m_extendedHeader.empty() && m_encryptedExtendedHeader.empty() ? 0x3U : (m_encryptedExtendedHeader.empty() ? 0x4U : 0x5U));
340 flags |= 0x80 | 0x40;
342 if (useCompression) {
345 m_fwriter.writeByte(flags);
348 if (!m_extendedHeader.empty()) {
349 m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
350 m_fwriter.writeString(m_extendedHeader);
354 stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
355 buffstr.exceptions(ios_base::failbit | ios_base::badbit);
358 if (!m_encryptedExtendedHeader.empty()) {
359 m_fwriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
360 m_fwriter.writeString(m_encryptedExtendedHeader);
362 m_rootEntry->make(buffstr);
363 buffstr.seekp(0, ios_base::end);
364 auto size =
static_cast<size_t>(buffstr.tellp());
368 vector<char> decryptedData(
size, 0);
369 buffstr.read(decryptedData.data(),
static_cast<streamoff
>(
size));
370 vector<char> encryptedData;
373 if (useCompression) {
374 uLongf compressedSize = compressBound(
size);
375 encryptedData.resize(8 + compressedSize);
376 ConversionUtilities::LE::getBytes(static_cast<uint64>(
size), encryptedData.data());
378 compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()),
size)) {
380 throw runtime_error(
"Compressing failed. The source buffer was too small.");
382 throw runtime_error(
"Compressing failed. The destination buffer was too small.");
384 encryptedData.swap(decryptedData);
385 size = 8 + compressedSize;
389 if (
size > numeric_limits<int>::max()) {
394 if (!useEncryption) {
396 m_file.write(decryptedData.data(),
static_cast<streamsize
>(
size));
401 EVP_CIPHER_CTX *ctx =
nullptr;
403 int outlen1, outlen2;
404 encryptedData.resize(
size + 32);
405 if (RAND_bytes(iv,
aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) ==
nullptr 406 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
407 || EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
408 reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(
size))
410 || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
413 EVP_CIPHER_CTX_free(ctx);
416 auto errorCode = ERR_get_error();
421 msg += ERR_error_string(errorCode,
nullptr);
422 errorCode = ERR_get_error();
428 EVP_CIPHER_CTX_free(ctx);
433 m_file.write(encryptedData.data(),
static_cast<streamsize
>(outlen1 + outlen2));
453 m_extendedHeader.clear();
454 m_encryptedExtendedHeader.clear();
466 throw runtime_error(
"Root entry has not been created.");
468 fstream output(targetPath.c_str(), ios_base::out);
469 const auto printIndention = [&output](
int level) {
470 for (
int i = 0; i < level; ++i) {
474 function<void(const Entry *entry, int level)> printNode;
475 printNode = [&output, &printNode, &printIndention](
const Entry *entry,
int level) {
476 printIndention(level);
477 output <<
" - " << entry->label() << endl;
478 switch (entry->type()) {
480 for (
const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
481 printNode(child, level + 1);
485 for (
const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
486 printIndention(level);
487 output <<
" " << field.name();
488 for (
auto i = field.name().length(); i < 15; ++i) {
491 output << field.value() << endl;
495 printNode(m_rootEntry.get(), 0);
515 fstream backupFile(m_path +
".backup", ios::out | ios::trunc | ios::binary);
516 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
517 backupFile << m_file.rdbuf();
528 return m_rootEntry !=
nullptr;
536 return m_rootEntry.get();
544 return m_rootEntry.get();
552 if (m_file.is_open()) {
575 if (ConversionUtilities::startsWith(m_path,
"file:")) {
576 m_path = m_path.substr(5);
603 value.copy(m_password, 32, 0);
611 memset(m_password, 0, 32);
625 if (m_freader.readUInt32LE() != 0x7770616DU) {
630 const auto version = m_freader.readUInt32LE();
631 if (version == 0x1U || version == 0x2U) {
633 }
else if (version == 0x3U) {
634 return m_freader.readByte() & 0x80;
645 return m_file.is_open();
656 m_file.seekg(0, ios::end);
657 return static_cast<size_t>(m_file.tellg());
const std::string & path() const
Returns the current file path.
PasswordFile()
Constructs a new password file.
bool hasRootEntry() const
Returns an indication whether a root entry is present.
void clear()
Closes the file if opened.
The NodeEntry class acts as parent for other entries.
The PasswordFile class holds account information in the form of Entry and Field instances and provide...
void load()
Reads the contents of the file.
void open(bool readOnly=false)
Opens the file.
void close()
Closes the file if currently opened.
void write(bool useEncryption=true, bool useCompression=true)
Writes the current root entry to the file which is assumed to be opened and writeable.
void doBackup()
Creates a backup of the file.
Contains all IO related classes.
const NodeEntry * rootEntry() const
Returns the root entry if present or nullptr otherwise.
void exportToTextfile(const std::string &targetPath) const
Writes the current root entry to a plain text file.
bool isOpen() const
Returns an indication whether the file is open.
The Field class holds field information which consists of a name and a value and is able to serialize...
void clearPath()
Clears the current path.
bool isEncryptionUsed()
Returns an indication whether encryption is used if the file is open; returns always false otherwise...
const unsigned int aes256cbcIvSize
~PasswordFile()
Closes the file if still opened and destroys the instance.
void setPath(const std::string &value)
Sets the current file path.
void create()
Creates the file.
std::size_t size()
Returns the size of the file if the file is open; otherwise returns zero.
void generateRootEntry()
Generates a new root entry for the file.
const char * password() const
Returns the current password.
The exception that is thrown when an encryption/decryption error occurs.
void setPassword(const std::string &value)
Sets the current password.
void opened()
Handles the file being opened.
void save(bool useEncryption=true, bool useCompression=true)
Writes the current root entry to the file under path() replacing its previous contents.
The exception that is thrown when a parsing error occurs.
void clearPassword()
Clears the current password.
void clearEntries()
Removes the root element if one is present.
Instances of the Entry class form a hierarchic data strucutre used to store account information...