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>
45 : m_freader(BinaryReader(&m_file))
46 , m_fwriter(BinaryWriter(&m_file))
51 m_file.exceptions(ios_base::failbit | ios_base::badbit);
59 : m_freader(BinaryReader(&m_file))
60 , m_fwriter(BinaryWriter(&m_file))
65 m_file.exceptions(ios_base::failbit | ios_base::badbit);
74 : m_path(other.m_path)
75 , m_password(other.m_password)
76 , m_rootEntry(other.m_rootEntry ? make_unique<
NodeEntry>(*other.m_rootEntry) : nullptr)
77 , m_extendedHeader(other.m_extendedHeader)
78 , m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
79 , m_freader(BinaryReader(&m_file))
80 , m_fwriter(BinaryWriter(&m_file))
81 , m_version(other.m_version)
82 , m_openOptions(other.m_openOptions)
83 , m_saveOptions(other.m_saveOptions)
85 m_file.exceptions(ios_base::failbit | ios_base::badbit);
92 : m_path(std::move(other.m_path))
93 , m_password(std::move(other.m_password))
94 , m_rootEntry(std::move(other.m_rootEntry))
95 , m_extendedHeader(std::move(other.m_extendedHeader))
96 , m_encryptedExtendedHeader(std::move(other.m_encryptedExtendedHeader))
97 , m_file(std::move(other.m_file))
98 , m_freader(BinaryReader(&m_file))
99 , m_fwriter(BinaryWriter(&m_file))
100 , m_version(other.m_version)
101 , m_openOptions(other.m_openOptions)
102 , m_saveOptions(other.m_saveOptions)
120 if (m_path.empty()) {
121 throw std::ios_base::failure(
"Unable to open file because path is empty.");
125 m_openOptions = options;
136 m_file.seekg(0, ios_base::end);
137 if (m_file.tellg() == 0) {
138 throw std::ios_base::failure(
"File is empty.");
150 m_rootEntry.reset(
new NodeEntry(
"accounts"));
161 if (m_path.empty()) {
162 throw std::ios_base::failure(
"Unable to create file because path is empty.");
164 m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
177 if (!m_file.is_open()) {
185 if (m_freader.readUInt32LE() != 0x7770616DU) {
190 m_version = m_freader.readUInt32LE();
191 if (m_version > 0x6U) {
192 throw ParsingException(argsToString(
"Version \"", m_version,
"\" is unknown. Only versions 0 to 6 are supported."));
194 if (m_version >= 0x6U) {
197 bool decrypterUsed, ivUsed, compressionUsed;
198 if (m_version >= 0x3U) {
199 const auto flags = m_freader.readByte();
200 if ((decrypterUsed = flags & 0x80)) {
203 if ((compressionUsed = flags & 0x20)) {
206 ivUsed = flags & 0x40;
208 if ((decrypterUsed = m_version >= 0x1U)) {
211 compressionUsed =
false;
212 ivUsed = m_version == 0x2U;
218 if (m_version >= 0x4U) {
219 std::uint16_t extendedHeaderSize = m_freader.readUInt16BE();
220 m_extendedHeader = m_freader.readString(extendedHeaderSize);
222 m_extendedHeader.clear();
226 const auto headerSize =
static_cast<size_t>(m_file.tellg());
227 m_file.seekg(0, ios_base::end);
228 auto remainingSize =
static_cast<size_t>(m_file.tellg()) - headerSize;
229 m_file.seekg(
static_cast<streamoff
>(headerSize), ios_base::beg);
232 uint32_t hashCount = 0U;
234 if (remainingSize < 4) {
237 hashCount = m_freader.readUInt32BE();
243 if (decrypterUsed && ivUsed) {
250 if (!remainingSize) {
255 vector<char> rawData;
256 m_freader.read(rawData,
static_cast<streamoff
>(remainingSize));
257 vector<char> decryptedData;
259 if (remainingSize > numeric_limits<int>::max()) {
268 for (uint32_t i = 1; i < hashCount; ++i) {
276 EVP_CIPHER_CTX *ctx =
nullptr;
278 int outlen1, outlen2;
279 if ((ctx = EVP_CIPHER_CTX_new()) ==
nullptr || EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
280 || EVP_DecryptUpdate(ctx,
reinterpret_cast<unsigned char *
>(decryptedData.data()), &outlen1,
281 reinterpret_cast<unsigned char *
>(rawData.data()),
static_cast<int>(remainingSize))
283 || EVP_DecryptFinal_ex(ctx,
reinterpret_cast<unsigned char *
>(decryptedData.data()) + outlen1, &outlen2) != 1) {
286 EVP_CIPHER_CTX_free(ctx);
289 auto errorCode = ERR_get_error();
294 msg += ERR_error_string(errorCode,
nullptr);
295 errorCode = ERR_get_error();
301 EVP_CIPHER_CTX_free(ctx);
303 const auto decryptedSize = outlen1 + outlen2;
304 if (decryptedSize < 0) {
307 remainingSize =
static_cast<size_t>(decryptedSize);
308 if (!remainingSize) {
314 decryptedData.swap(rawData);
318 if (compressionUsed) {
319 if (remainingSize < 8) {
322 if (remainingSize > numeric_limits<uLongf>::max()) {
325 const auto rawDecompressedSize = LE::toUInt64(decryptedData.data());
326 if (rawDecompressedSize > numeric_limits<uLongf>::max()) {
329 auto decompressedSize =
static_cast<uLongf
>(rawDecompressedSize);
330 rawData.resize(decompressedSize);
331 switch (uncompress(
reinterpret_cast<Bytef *
>(rawData.data()), &decompressedSize,
reinterpret_cast<Bytef *
>(decryptedData.data() + 8),
332 static_cast<uLongf
>(remainingSize - 8))) {
334 throw ParsingException(
"Decompressing failed. The source buffer was too small.");
336 throw ParsingException(
"Decompressing failed. The destination buffer was too small.");
338 throw ParsingException(
"Decompressing failed. The input data was corrupted or incomplete.");
340 decryptedData.swap(rawData);
341 remainingSize = decompressedSize;
344 if (!remainingSize) {
349 stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
350 decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
352#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
353 decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(),
static_cast<streamsize
>(remainingSize));
355 decryptedStream.write(decryptedData.data(),
static_cast<streamsize
>(remainingSize));
357 if (m_version >= 0x5u) {
358 BinaryReader reader(&decryptedStream);
359 const auto extendedHeaderSize = reader.readUInt16BE();
360 m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
362 m_encryptedExtendedHeader.clear();
364 m_rootEntry.reset(
new NodeEntry(decryptedStream));
365 }
catch (
const std::ios_base::failure &failure) {
366 if (decryptedStream.eof()) {
369 throw ParsingException(argsToString(
"An IO error occurred when reading internal buffer: ", failure.what()));
381 }
else if (!m_encryptedExtendedHeader.empty()) {
383 }
else if (!m_extendedHeader.empty()) {
399 throw runtime_error(
"Root entry has not been created.");
407 if (m_file.is_open()) {
411 m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
412 }
catch (
const ios_base::failure &) {
417 m_file.open(m_path, ios_base::out | ios_base::trunc | ios_base::binary);
435 throw runtime_error(
"Root entry has not been created.");
439 m_fwriter.writeUInt32LE(0x7770616DU);
443 m_fwriter.writeUInt32LE(
version);
446 std::uint8_t flags = 0x00;
448 flags |= 0x80 | 0x40;
453 m_fwriter.writeByte(flags);
457 if (m_extendedHeader.size() > numeric_limits<std::uint16_t>::max()) {
458 throw runtime_error(
"Extended header exceeds maximum size.");
460 m_fwriter.writeUInt16BE(
static_cast<std::uint16_t
>(m_extendedHeader.size()));
461 m_fwriter.writeString(m_extendedHeader);
465 stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
466 buffstr.exceptions(ios_base::failbit | ios_base::badbit);
470 if (m_encryptedExtendedHeader.size() > numeric_limits<std::uint16_t>::max()) {
471 throw runtime_error(
"Encrypted extended header exceeds maximum size.");
473 BinaryWriter buffstrWriter(&buffstr);
474 buffstrWriter.writeUInt16BE(
static_cast<std::uint16_t
>(m_encryptedExtendedHeader.size()));
475 buffstrWriter.writeString(m_encryptedExtendedHeader);
477 m_rootEntry->make(buffstr);
478 buffstr.seekp(0, ios_base::end);
479 auto size =
static_cast<size_t>(buffstr.tellp());
483 vector<char> decryptedData(
size, 0);
484 buffstr.read(decryptedData.data(),
static_cast<streamoff
>(
size));
485 vector<char> encryptedData;
489 uLongf compressedSize = compressBound(
size);
490 encryptedData.resize(8 + compressedSize);
491 LE::getBytes(
static_cast<std::uint64_t
>(
size), encryptedData.data());
493 compress(
reinterpret_cast<Bytef *
>(encryptedData.data() + 8), &compressedSize,
reinterpret_cast<Bytef *
>(decryptedData.data()),
size)) {
495 throw runtime_error(
"Compressing failed. The source buffer was too small.");
497 throw runtime_error(
"Compressing failed. The destination buffer was too small.");
499 encryptedData.swap(decryptedData);
500 size = 8 + compressedSize;
504 if (
size > numeric_limits<int>::max()) {
511 m_file.write(decryptedData.data(),
static_cast<streamsize
>(
size));
522 for (uint32_t i = 1; i < hashCount; ++i) {
530 EVP_CIPHER_CTX *ctx =
nullptr;
532 int outlen1, outlen2;
534 if (RAND_bytes(iv,
aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) ==
nullptr
535 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
536 || EVP_EncryptUpdate(ctx,
reinterpret_cast<unsigned char *
>(encryptedData.data()), &outlen1,
537 reinterpret_cast<unsigned char *
>(decryptedData.data()),
static_cast<int>(
size))
539 || EVP_EncryptFinal_ex(ctx,
reinterpret_cast<unsigned char *
>(encryptedData.data()) + outlen1, &outlen2) != 1) {
542 EVP_CIPHER_CTX_free(ctx);
545 auto errorCode = ERR_get_error();
550 msg += ERR_error_string(errorCode,
nullptr);
551 errorCode = ERR_get_error();
557 EVP_CIPHER_CTX_free(ctx);
562 m_fwriter.writeUInt32BE(hashCount);
565 m_file.write(encryptedData.data(),
static_cast<streamsize
>(outlen1 + outlen2));
587 m_extendedHeader.clear();
588 m_encryptedExtendedHeader.clear();
599 throw runtime_error(
"Root entry has not been created.");
601 NativeFileStream output;
602 output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
603 output.open(targetPath, std::ios_base::out);
604 const auto printIndention = [&output](
int level) {
605 for (
int i = 0; i < level; ++i) {
609 function<void(
const Entry *entry,
int level)> printNode;
610 printNode = [&output, &printNode, &printIndention](
const Entry *entry,
int level) {
611 printIndention(level);
612 output <<
" - " << entry->
label() << endl;
613 switch (entry->
type()) {
615 for (
const Entry *child :
static_cast<const NodeEntry *
>(entry)->children()) {
616 printNode(child, level + 1);
621 printIndention(level);
622 output <<
" " << field.name();
623 for (
auto i = field.name().length(); i < 15; ++i) {
626 output << field.value() << endl;
630 printNode(m_rootEntry.get(), 0);
650 fstream backupFile(m_path +
".backup", ios::out | ios::trunc | ios::binary);
651 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
652 backupFile << m_file.rdbuf();
663 return m_rootEntry !=
nullptr;
671 return m_rootEntry.get();
679 return m_rootEntry.get();
687 if (m_file.is_open()) {
702 if (startsWith(m_path,
"file:")) {
703 m_path = m_path.substr(5);
720 if (m_freader.readUInt32LE() != 0x7770616DU) {
725 const auto version = m_freader.readUInt32LE();
729 return m_freader.readByte() & 0x80;
743 m_file.seekg(0, ios::end);
744 return static_cast<size_t>(m_file.tellg());
752 string result =
"<table>";
753 if (!m_path.empty()) {
754 result += argsToString(
"<tr><td>Path:</td><td>", m_path,
"</td></tr>");
756 result += argsToString(
"<tr><td>Version:</td><td>", m_version,
"</td></tr>");
758 if (m_version != minVersion) {
759 result += argsToString(
"<tr><td></td><td>(on disk, after saving: ", minVersion,
")</td></tr>");
761 result += argsToString(
"<tr><td>Features:</td><td>",
flagsToString(m_saveOptions),
"</td></tr>");
765 const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() :
EntryStatistics();
766 result += argsToString(
"<tr><td>Number of categories:</td><td>", stats.nodeCount,
"</td></tr><tr><td>Number of accounts:</td><td>",
767 stats.accountCount,
"</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount,
"</td></tr></table>");
776 vector<string> options;
778 options.emplace_back(
"read-only");
780 if (options.empty()) {
781 options.emplace_back(
"none");
783 return joinStrings(options,
", ");
791 vector<string> options;
794 options.emplace_back(
"encryption");
797 options.emplace_back(
"compression");
800 options.emplace_back(
"password hashing");
802 if (options.empty()) {
803 options.emplace_back(
"none");
805 return joinStrings(options,
", ");
The exception that is thrown when a parsing error occurs.
The exception that is thrown when an encryption/decryption error occurs.
Instances of the Entry class form a hierarchic data structure used to store account information.
virtual EntryType type() const =0
Returns the type of the entry.
const std::string & label() const
Returns the label.
The Field class holds field information which consists of a name and a value and is able to serialize...
The NodeEntry class acts as parent for other entries.
The exception that is thrown when a parsing error occurs.
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.
bool isOpen() const
Returns an indication whether the file is open.
void clear()
Closes the file if opened.
void opened()
Handles the file being opened.
void clearEntries()
Removes the root element if one is present.
void open(PasswordFileOpenFlags options=PasswordFileOpenFlags::Default)
Opens the file.
bool hasRootEntry() const
Returns an indication whether a root entry is present.
void create()
Creates the file.
void exportToTextfile(const std::string &targetPath) const
Writes the current root entry to a plain text 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 clearPassword()
Clears the current password.
std::string summary(PasswordFileSaveFlags saveOptions) const
Returns a summary about the file (version, used features, statistics).
PasswordFile()
Constructs a new password file.
const std::string & password() const
Returns the current password.
void setPassword(const std::string &password)
Sets the current password.
const std::string & path() const
Returns the current file path.
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.
~PasswordFile()
Closes the file if still opened and destroys the instance.
void save(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file under path() replacing its previous contents.
void setPath(const std::string &value)
Sets the current file path.
std::uint32_t mininumVersion(PasswordFileSaveFlags options) const
Returns the minimum file version required to write the current instance with the specified options.
void write(PasswordFileSaveFlags options=PasswordFileSaveFlags::Default)
Writes the current root entry to the file which is assumed to be opened and writeable.
void generateRootEntry()
Generates a new root entry for the file.
void clearPath()
Clears the current path.
void doBackup()
Creates a backup of the file.
PasswordFileSaveFlags saveOptions() const
Returns the save options used the last time when saving the file.
Contains all IO related classes.
PASSWORD_FILE_EXPORT std::string flagsToString(PasswordFileOpenFlags flags)
Returns a comma-separated string for the specified flags.
constexpr unsigned int aes256additionalBufferSize
constexpr unsigned int aes256cbcIvSize
constexpr unsigned int aes256blockSize
PASSWORD_FILE_EXPORT std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
PASSWORD_FILE_EXPORT Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
static constexpr std::size_t size