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 empty.");
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.");
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));
520 for (uint32_t i = 1; i < hashCount; ++i) {
528 EVP_CIPHER_CTX *ctx =
nullptr;
530 int outlen1, outlen2;
531 encryptedData.resize(
size + 32);
532 if (RAND_bytes(iv,
aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) ==
nullptr
533 || EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(),
nullptr,
password.data, iv) != 1
534 || EVP_EncryptUpdate(ctx,
reinterpret_cast<unsigned char *
>(encryptedData.data()), &outlen1,
535 reinterpret_cast<unsigned char *
>(decryptedData.data()),
static_cast<int>(
size))
537 || EVP_EncryptFinal_ex(ctx,
reinterpret_cast<unsigned char *
>(encryptedData.data()) + outlen1, &outlen2) != 1) {
540 EVP_CIPHER_CTX_free(ctx);
543 auto errorCode = ERR_get_error();
548 msg += ERR_error_string(errorCode,
nullptr);
549 errorCode = ERR_get_error();
555 EVP_CIPHER_CTX_free(ctx);
560 m_fwriter.writeUInt32BE(hashCount);
563 m_file.write(encryptedData.data(),
static_cast<streamsize
>(outlen1 + outlen2));
585 m_extendedHeader.clear();
586 m_encryptedExtendedHeader.clear();
597 throw runtime_error(
"Root entry has not been created.");
599 NativeFileStream output;
600 output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
601 output.open(targetPath, std::ios_base::out);
602 const auto printIndention = [&output](
int level) {
603 for (
int i = 0; i < level; ++i) {
607 function<void(
const Entry *entry,
int level)> printNode;
608 printNode = [&output, &printNode, &printIndention](
const Entry *entry,
int level) {
609 printIndention(level);
610 output <<
" - " << entry->
label() << endl;
611 switch (entry->
type()) {
613 for (
const Entry *child :
static_cast<const NodeEntry *
>(entry)->children()) {
614 printNode(child, level + 1);
619 printIndention(level);
620 output <<
" " << field.name();
621 for (
auto i = field.name().length(); i < 15; ++i) {
624 output << field.value() << endl;
628 printNode(m_rootEntry.get(), 0);
648 fstream backupFile(m_path +
".backup", ios::out | ios::trunc | ios::binary);
649 backupFile.exceptions(ios_base::failbit | ios_base::badbit);
650 backupFile << m_file.rdbuf();
661 return m_rootEntry !=
nullptr;
669 return m_rootEntry.get();
677 return m_rootEntry.get();
685 if (m_file.is_open()) {
700 if (startsWith(m_path,
"file:")) {
701 m_path = m_path.substr(5);
718 if (m_freader.readUInt32LE() != 0x7770616DU) {
723 const auto version = m_freader.readUInt32LE();
727 return m_freader.readByte() & 0x80;
741 m_file.seekg(0, ios::end);
742 return static_cast<size_t>(m_file.tellg());
750 string result =
"<table>";
751 if (!m_path.empty()) {
752 result += argsToString(
"<tr><td>Path:</td><td>", m_path,
"</td></tr>");
754 result += argsToString(
"<tr><td>Version:</td><td>", m_version,
"</td></tr>");
756 if (m_version != minVersion) {
757 result += argsToString(
"<tr><td></td><td>(on disk, after saving: ", minVersion,
")</td></tr>");
759 result += argsToString(
"<tr><td>Features:</td><td>",
flagsToString(m_saveOptions),
"</td></tr>");
763 const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() :
EntryStatistics();
764 result += argsToString(
"<tr><td>Number of categories:</td><td>", stats.nodeCount,
"</td></tr><tr><td>Number of accounts:</td><td>",
765 stats.accountCount,
"</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount,
"</td></tr></table>");
774 vector<string> options;
776 options.emplace_back(
"read-only");
778 if (options.empty()) {
779 options.emplace_back(
"none");
781 return joinStrings(options,
", ");
789 vector<string> options;
792 options.emplace_back(
"encryption");
795 options.emplace_back(
"compression");
798 options.emplace_back(
"password hashing");
800 if (options.empty()) {
801 options.emplace_back(
"none");
803 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.
const unsigned int aes256cbcIvSize
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