6 #include "../util/openssl.h"
7 #include "../util/opensslrandomdevice.h"
9 #include <c++utilities/conversion/stringbuilder.h>
10 #include <c++utilities/conversion/stringconversion.h>
12 #include <openssl/conf.h>
13 #include <openssl/err.h>
14 #include <openssl/evp.h>
15 #include <openssl/rand.h>
42 PasswordFile::PasswordFile()
43 : m_freader(BinaryReader(&m_file))
44 , m_fwriter(BinaryWriter(&m_file))
49 m_file.exceptions(ios_base::failbit | ios_base::badbit);
57 : m_freader(BinaryReader(&m_file))
58 , m_fwriter(BinaryWriter(&m_file))
63 m_file.exceptions(ios_base::failbit | ios_base::badbit);
72 : m_path(other.m_path)
73 , m_password(other.m_password)
74 , m_rootEntry(other.m_rootEntry ? make_unique<
NodeEntry>(*other.m_rootEntry) : nullptr)
75 , m_extendedHeader(other.m_extendedHeader)
76 , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
77 , m_freader(BinaryReader(&m_file))
78 , m_fwriter(BinaryWriter(&m_file))
79 , m_version(other.m_version)
80 , m_openOptions(other.m_openOptions)
81 , m_saveOptions(other.m_saveOptions)
83 m_file.exceptions(ios_base::failbit | ios_base::badbit);
90 : m_path(move(other.m_path))
91 , m_password(move(other.m_password))
92 , m_rootEntry(move(other.m_rootEntry))
93 , m_extendedHeader(move(other.m_extendedHeader))
94 , m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
95 , m_file(move(other.m_file))
96 , m_freader(BinaryReader(&m_file))
97 , m_fwriter(BinaryWriter(&m_file))
98 , m_version(other.m_version)
99 , m_openOptions(other.m_openOptions)
100 , m_saveOptions(other.m_saveOptions)
118 if (m_path.empty()) {
119 throw std::ios_base::failure(
"Unable to open file because path is emtpy.");
123 m_openOptions = options;
134 m_file.seekg(0, ios_base::end);
135 if (m_file.tellg() == 0) {
136 throw std::ios_base::failure(
"File is empty.");
148 m_rootEntry.reset(
new NodeEntry(
"accounts"));
159 if (m_path.empty()) {
160 throw std::ios_base::failure(
"Unable to create file because path is empty.");
162 m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
175 if (!m_file.is_open()) {
183 if (m_freader.readUInt32LE() != 0x7770616DU) {
188 m_version = m_freader.readUInt32LE();
189 if (m_version > 0x6U) {
190 throw ParsingException(argsToString(
"Version \"", m_version,
"\" is unknown. Only versions 0 to 6 are supported."));
192 if (m_version >= 0x6U) {
195 bool decrypterUsed, ivUsed, compressionUsed;
196 if (m_version >= 0x3U) {
197 const auto flags = m_freader.readByte();
198 if ((decrypterUsed = flags & 0x80)) {
201 if ((compressionUsed = flags & 0x20)) {
204 ivUsed = flags & 0x40;
206 if ((decrypterUsed = m_version >= 0x1U)) {
209 compressionUsed =
false;
210 ivUsed = m_version == 0x2U;
216 if (m_version >= 0x4U) {
217 std::uint16_t extendedHeaderSize = m_freader.readUInt16BE();
218 m_extendedHeader = m_freader.readString(extendedHeaderSize);
220 m_extendedHeader.clear();
224 const auto headerSize = static_cast<size_t>(m_file.tellg());
225 m_file.seekg(0, ios_base::end);
226 auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
227 m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
230 uint32_t hashCount = 0U;
232 if (remainingSize < 4) {
235 hashCount = m_freader.readUInt32BE();
241 if (decrypterUsed && ivUsed) {
248 if (!remainingSize) {
253 vector<char> rawData;
254 m_freader.read(rawData, static_cast<streamoff>(remainingSize));
255 vector<char> decryptedData;
257 if (remainingSize > numeric_limits<int>::max()) {
266 for (uint32_t i = 1; i < hashCount; ++i) {
274 EVP_CIPHER_CTX *ctx =
nullptr;
275 decryptedData.resize(remainingSize + 32);
276 int outlen1, outlen2;
277 if ((ctx = EVP_CIPHER_CTX_new()) ==
nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
278 || EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
279 reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
281 || EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
284 EVP_CIPHER_CTX_free(ctx);
287 auto errorCode = ERR_get_error();
292 msg += ERR_error_string(errorCode,
nullptr);
293 errorCode = ERR_get_error();
299 EVP_CIPHER_CTX_free(ctx);
301 const auto decryptedSize = outlen1 + outlen2;
302 if (decryptedSize < 0) {
305 remainingSize = static_cast<size_t>(decryptedSize);
306 if (!remainingSize) {
312 decryptedData.swap(rawData);
316 if (compressionUsed) {
317 if (remainingSize < 8) {
320 if (remainingSize > numeric_limits<uLongf>::max()) {
323 const auto rawDecompressedSize = LE::toUInt64(decryptedData.data());
324 if (rawDecompressedSize > numeric_limits<uLongf>::max()) {
327 auto decompressedSize = static_cast<uLongf>(rawDecompressedSize);
328 rawData.resize(decompressedSize);
329 switch (uncompress(reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8),
330 static_cast<uLongf>(remainingSize - 8))) {
332 throw ParsingException(
"Decompressing failed. The source buffer was too small.");
334 throw ParsingException(
"Decompressing failed. The destination buffer was too small.");
336 throw ParsingException(
"Decompressing failed. The input data was corrupted or incomplete.");
338 decryptedData.swap(rawData);
339 remainingSize = decompressedSize;
342 if (!remainingSize) {
347 stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
348 decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
350 #ifdef _LIBCPP_VERSION
351 decryptedStream.write(decryptedData.data(), static_cast<streamsize>(remainingSize));
353 decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
355 if (m_version >= 0x5u) {
356 BinaryReader reader(&decryptedStream);
357 const auto extendedHeaderSize = reader.readUInt16BE();
358 m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
360 m_encryptedExtendedHeader.clear();
362 m_rootEntry.reset(
new NodeEntry(decryptedStream));
363 }
catch (
const std::ios_base::failure &failure) {
364 if (decryptedStream.eof()) {
367 throw ParsingException(argsToString(
"An IO error occurred when reading internal buffer: ", failure.what()));
379 }
else if (!m_encryptedExtendedHeader.empty()) {
381 }
else if (!m_extendedHeader.empty()) {
397 throw runtime_error(
"Root entry has not been created.");
401 if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
405 if (m_file.is_open()) {
409 m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
410 }
catch (
const ios_base::failure &) {
415 m_file.open(m_path, ios_base::out | ios_base::trunc | ios_base::binary);
433 throw runtime_error(
"Root entry has not been created.");
437 m_fwriter.writeUInt32LE(0x7770616DU);
441 m_fwriter.writeUInt32LE(
version);
444 std::uint8_t flags = 0x00;
446 flags |= 0x80 | 0x40;
451 m_fwriter.writeByte(flags);
455 if (m_extendedHeader.size() > numeric_limits<std::uint16_t>::max()) {
456 throw runtime_error(
"Extended header exceeds maximum size.");
458 m_fwriter.writeUInt16BE(static_cast<std::uint16_t>(m_extendedHeader.size()));
459 m_fwriter.writeString(m_extendedHeader);
463 stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
464 buffstr.exceptions(ios_base::failbit | ios_base::badbit);
468 if (m_encryptedExtendedHeader.size() > numeric_limits<std::uint16_t>::max()) {
469 throw runtime_error(
"Encrypted extended header exceeds maximum size.");
471 BinaryWriter buffstrWriter(&buffstr);
472 buffstrWriter.writeUInt16BE(static_cast<std::uint16_t>(m_encryptedExtendedHeader.size()));
473 buffstrWriter.writeString(m_encryptedExtendedHeader);
475 m_rootEntry->make(buffstr);
476 buffstr.seekp(0, ios_base::end);
477 auto size = static_cast<size_t>(buffstr.tellp());
481 vector<char> decryptedData(
size, 0);
482 buffstr.read(decryptedData.data(), static_cast<streamoff>(
size));
483 vector<char> encryptedData;
487 uLongf compressedSize = compressBound(
size);
488 encryptedData.resize(8 + compressedSize);
489 LE::getBytes(static_cast<std::uint64_t>(
size), encryptedData.data());
491 compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()),
size)) {
493 throw runtime_error(
"Compressing failed. The source buffer was too small.");
495 throw runtime_error(
"Compressing failed. The destination buffer was too small.");
497 encryptedData.swap(decryptedData);
498 size = 8 + compressedSize;
502 if (
size > numeric_limits<int>::max()) {
509 m_file.write(decryptedData.data(), static_cast<streamsize>(
size));
519 for (uint32_t i = 1; i < hashCount; ++i) {
527 EVP_CIPHER_CTX *ctx =
nullptr;
529 int outlen1, outlen2;
530 encryptedData.resize(
size + 32);
531 if (RAND_bytes(iv,
aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) ==
nullptr
532 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
533 || EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
534 reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(
size))
536 || EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
539 EVP_CIPHER_CTX_free(ctx);
542 auto errorCode = ERR_get_error();
547 msg += ERR_error_string(errorCode,
nullptr);
548 errorCode = ERR_get_error();
554 EVP_CIPHER_CTX_free(ctx);
559 m_fwriter.writeUInt32BE(hashCount);
562 m_file.write(encryptedData.data(), static_cast<streamsize>(outlen1 + outlen2));
583 m_extendedHeader.clear();
584 m_encryptedExtendedHeader.clear();
596 throw runtime_error(
"Root entry has not been created.");
598 fstream output(targetPath.c_str(), ios_base::out);
599 const auto printIndention = [&output](
int level) {
600 for (
int i = 0; i < level; ++i) {
604 function<void(
const Entry *entry,
int level)> printNode;
605 printNode = [&output, &printNode, &printIndention](
const Entry *entry,
int level) {
606 printIndention(level);
607 output <<
" - " << entry->label() << endl;
608 switch (entry->type()) {
610 for (
const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
611 printNode(child, level + 1);
615 for (
const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
616 printIndention(level);
617 output <<
" " << field.name();
618 for (
auto i = field.name().length(); i < 15; ++i) {
621 output << field.value() << endl;
625 printNode(m_rootEntry.get(), 0);
645 fstream backupFile(m_path +
".backup", ios::out | ios::trunc | ios::binary);
646 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
647 backupFile << m_file.rdbuf();
658 return m_rootEntry !=
nullptr;
666 return m_rootEntry.get();
674 return m_rootEntry.get();
682 if (m_file.is_open()) {
697 if (startsWith(m_path,
"file:")) {
698 m_path = m_path.substr(5);
715 if (m_freader.readUInt32LE() != 0x7770616DU) {
720 const auto version = m_freader.readUInt32LE();
724 return m_freader.readByte() & 0x80;
738 m_file.seekg(0, ios::end);
739 return static_cast<size_t>(m_file.tellg());
747 string result =
"<table>";
748 if (!m_path.empty()) {
749 result += argsToString(
"<tr><td>Path:</td><td>", m_path,
"</td></tr>");
751 result += argsToString(
"<tr><td>Version:</td><td>", m_version,
"</td></tr>");
753 if (m_version != minVersion) {
754 result += argsToString(
"<tr><td></td><td>(on disk, after saving: ", minVersion,
")</td></tr>");
756 result += argsToString(
"<tr><td>Features:</td><td>",
flagsToString(m_saveOptions),
"</td></tr>");
760 const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() :
EntryStatistics();
761 result += argsToString(
"<tr><td>Number of categories:</td><td>", stats.nodeCount,
"</td></tr><tr><td>Number of accounts:</td><td>",
762 stats.accountCount,
"</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount,
"</td></tr></table>");
771 vector<string> options;
773 options.emplace_back(
"read-only");
775 if (options.empty()) {
776 options.emplace_back(
"none");
778 return joinStrings(options,
", ");
786 vector<string> options;
789 options.emplace_back(
"encryption");
792 options.emplace_back(
"compression");
795 options.emplace_back(
"password hashing");
797 if (options.empty()) {
798 options.emplace_back(
"none");
800 return joinStrings(options,
", ");